nuttx/net/usrsock/usrsock_devif.c

745 lines
19 KiB
C
Raw Permalink Normal View History

/****************************************************************************
* net/usrsock/usrsock_devif.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#if defined(CONFIG_NET) && defined(CONFIG_NET_USRSOCK)
#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/random.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/net/net.h>
#include <nuttx/net/usrsock.h>
#include "usrsock/usrsock.h"
/****************************************************************************
* Private Types
****************************************************************************/
struct usrsock_req_s
{
mutex_t lock; /* Request mutex (only one outstanding
* request) */
sem_t acksem; /* Request acknowledgment notification */
uint32_t newxid; /* New transcation Id */
uint32_t ackxid; /* Exchange id for which waiting ack */
uint16_t nbusy; /* Number of requests blocked from different
* threads */
/* Connection instance to receive data buffers. */
FAR struct usrsock_conn_s *datain_conn;
};
/****************************************************************************
* Private Data
****************************************************************************/
/* only support 1 usrsock network interface for the moment,
* define it into array or construct a list
* if multiple usrsock network interfaces are needed in the future
*/
static struct usrsock_req_s g_usrsock_req =
{
NXMUTEX_INITIALIZER,
SEM_INITIALIZER(0),
0,
0,
0,
NULL
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: usrsock_iovec_do() - copy to/from iovec from/to buffer.
****************************************************************************/
static ssize_t usrsock_iovec_do(FAR void *srcdst, size_t srcdstlen,
FAR struct iovec *iov, int iovcnt,
size_t pos, bool from_iov, FAR bool *done)
{
FAR uint8_t *ioout = srcdst;
FAR uint8_t *iovbuf;
ssize_t total = 0;
size_t srclen = 0;
/* Rewind to correct position. */
while (pos >= 0 && iovcnt > 0)
{
if (iov->iov_len <= pos)
{
pos -= iov->iov_len;
iov++;
iovcnt--;
}
else
{
break;
}
}
if (iovcnt == 0)
{
/* Position beyond iovec. */
total = -EINVAL;
goto out;
}
iovbuf = iov->iov_base;
srclen = iov->iov_len;
iovbuf += pos;
srclen -= pos;
iov++;
iovcnt--;
while ((srclen > 0 || iovcnt > 0) && srcdstlen > 0)
{
size_t clen = srclen;
if (srclen == 0)
{
/* Skip empty iovec. */
iovbuf = iov->iov_base;
srclen = iov->iov_len;
iov++;
iovcnt--;
continue;
}
if (clen > srcdstlen)
{
clen = srcdstlen;
}
if (from_iov)
{
memmove(ioout, iovbuf, clen);
}
else
{
memmove(iovbuf, ioout, clen);
}
ioout += clen;
srcdstlen -= clen;
iovbuf += clen;
srclen -= clen;
total += clen;
if (srclen == 0)
{
if (iovcnt == 0)
{
break;
}
iovbuf = iov->iov_base;
srclen = iov->iov_len;
iov++;
iovcnt--;
}
}
out:
if (done)
{
*done = !srclen && !iovcnt;
}
return total;
}
/****************************************************************************
* Name: usrsock_handle_event
****************************************************************************/
static ssize_t usrsock_handle_event(FAR const void *buffer, size_t len)
{
FAR const struct usrsock_message_common_s *common = buffer;
switch (common->msgid)
{
case USRSOCK_MESSAGE_SOCKET_EVENT:
{
FAR const struct usrsock_message_socket_event_s *hdr = buffer;
FAR struct usrsock_conn_s *conn;
int ret;
if (len < sizeof(*hdr))
{
nerr("message too short, %zu < %zu.\n", len, sizeof(*hdr));
return -EINVAL;
}
len = sizeof(*hdr);
/* Get corresponding usrsock connection. */
conn = usrsock_active(hdr->usockid);
if (!conn)
{
nerr("no active connection for usockid=%d, events=%x.\n",
hdr->usockid, hdr->head.events);
/* We may receive event after socket close if message from lower
* layer is out of order, but it's OK to ignore such error.
*/
return len;
}
#ifdef CONFIG_DEV_RANDOM
/* Add randomness. */
add_sw_randomness((hdr->head.events << 16) - hdr->usockid);
#endif
/* Handle event. */
conn->resp.events = hdr->head.events & ~USRSOCK_EVENT_INTERNAL_MASK;
ret = usrsock_event(conn);
if (ret < 0)
{
return ret;
}
}
break;
default:
nerr("Unknown event type: %d\n", common->msgid);
return -EINVAL;
}
return len;
}
/****************************************************************************
* Name: usrsock_handle_response
****************************************************************************/
static ssize_t usrsock_handle_response(FAR struct usrsock_conn_s *conn,
FAR const void *buffer,
size_t len)
{
FAR const struct usrsock_message_req_ack_s *hdr = buffer;
if (USRSOCK_MESSAGE_REQ_IN_PROGRESS(hdr->head.flags))
{
/* In-progress response is acknowledgment that response was
* received.
*/
conn->resp.inprogress = true;
/* This branch indicates successful processing and waiting
* for USRSOCK_EVENT_CONNECT_READY event.
*/
conn->resp.result = 0;
}
else
{
conn->resp.inprogress = false;
conn->resp.xid = 0;
/* Get result for common request. */
conn->resp.result = hdr->result;
/* Done with request/response. */
usrsock_event(conn);
}
return sizeof(*hdr);
}
/****************************************************************************
* Name: usrsock_handle_datareq_response
****************************************************************************/
static ssize_t
usrsock_handle_datareq_response(FAR struct usrsock_conn_s *conn,
FAR const void *buffer,
size_t len)
{
FAR const struct usrsock_message_datareq_ack_s *datahdr = buffer;
FAR const struct usrsock_message_req_ack_s *hdr = &datahdr->reqack;
FAR struct usrsock_req_s *req = &g_usrsock_req;
int num_inbufs;
int iovpos;
if (USRSOCK_MESSAGE_REQ_IN_PROGRESS(hdr->head.flags))
{
if (datahdr->reqack.result > 0)
{
ninfo("error: request in progress, and result > 0.\n");
return -EINVAL;
}
else if (datahdr->valuelen > 0)
{
ninfo("error: request in progress, and valuelen > 0.\n");
return -EINVAL;
}
/* In-progress response is acknowledgment that response was
* received.
*/
conn->resp.inprogress = true;
/* This branch indicates successful processing and waiting
* for USRSOCK_EVENT_CONNECT_READY event.
*/
conn->resp.result = 0;
return sizeof(*datahdr);
}
conn->resp.inprogress = false;
conn->resp.xid = 0;
/* Prepare to read buffers. */
conn->resp.result = hdr->result;
conn->resp.valuelen = datahdr->valuelen;
conn->resp.valuelen_nontrunc = datahdr->valuelen_nontrunc;
if (conn->resp.result < 0)
{
/* Error, valuelen must be zero. */
if (datahdr->valuelen > 0 || datahdr->valuelen_nontrunc > 0)
{
nerr("error: response result negative, and valuelen or "
"valuelen_nontrunc non-zero.\n");
return -EINVAL;
}
/* Done with request/response. */
usrsock_event(conn);
return sizeof(*datahdr);
}
/* Check that number of buffers match available. */
num_inbufs = (hdr->result > 0) + 1;
if (conn->resp.datain.iovcnt < num_inbufs)
{
nwarn("not enough recv buffers (need: %d, have: %d).\n", num_inbufs,
conn->resp.datain.iovcnt);
return -EINVAL;
}
/* Adjust length of receiving buffers. */
conn->resp.datain.total = 0;
iovpos = 0;
/* Value buffer is always the first */
if (conn->resp.datain.iov[iovpos].iov_len < datahdr->valuelen)
{
nwarn("%dth buffer not large enough (need: %d, have: %zu).\n",
iovpos, datahdr->valuelen,
conn->resp.datain.iov[iovpos].iov_len);
return -EINVAL;
}
/* Adjust read size. */
conn->resp.datain.iov[iovpos].iov_len = datahdr->valuelen;
conn->resp.datain.total += conn->resp.datain.iov[iovpos].iov_len;
iovpos++;
if (hdr->result > 0)
{
/* Value buffer is always the first */
if (conn->resp.datain.iov[iovpos].iov_len < hdr->result)
{
nwarn("%dth buffer not large enough "
"(need: %" PRId32 ", have: %zu).\n",
iovpos, hdr->result,
conn->resp.datain.iov[iovpos].iov_len);
return -EINVAL;
}
/* Adjust read size. */
conn->resp.datain.iov[iovpos].iov_len = hdr->result;
conn->resp.datain.total += conn->resp.datain.iov[iovpos].iov_len;
iovpos++;
}
DEBUGASSERT(num_inbufs == iovpos);
conn->resp.datain.iovcnt = num_inbufs;
/* Next written buffers are redirected to data buffers. */
req->datain_conn = conn;
return sizeof(*datahdr);
}
/****************************************************************************
* Name: usrsock_handle_req_response
****************************************************************************/
static ssize_t usrsock_handle_req_response(FAR const void *buffer,
size_t len, FAR bool *req_done)
{
FAR const struct usrsock_message_req_ack_s *hdr = buffer;
FAR struct usrsock_conn_s *conn = NULL;
FAR struct usrsock_req_s *req = &g_usrsock_req;
ssize_t (*handle_response)(FAR struct usrsock_conn_s *conn,
FAR const void *buffer,
size_t len);
size_t hdrlen;
ssize_t ret;
switch (hdr->head.msgid)
{
case USRSOCK_MESSAGE_RESPONSE_ACK:
hdrlen = sizeof(struct usrsock_message_req_ack_s);
handle_response = &usrsock_handle_response;
break;
case USRSOCK_MESSAGE_RESPONSE_DATA_ACK:
hdrlen = sizeof(struct usrsock_message_datareq_ack_s);
handle_response = &usrsock_handle_datareq_response;
break;
default:
nerr("unknown message type: %d, flags: %d, xid: %" PRIu32 ", "
"result: %" PRId32 "\n",
hdr->head.msgid, hdr->head.flags, hdr->xid, hdr->result);
return -EINVAL;
}
if (len < hdrlen)
{
nerr("message too short, %zu < %zu.\n", len, hdrlen);
return -EINVAL;
}
net_lock();
/* Get corresponding usrsock connection for this transfer */
while ((conn = usrsock_nextconn(conn)) != NULL &&
conn->resp.xid != hdr->xid);
if (!conn)
{
/* No connection waiting for this message. */
nerr("Could find connection waiting for response"
"with xid=%" PRIu32 "\n", hdr->xid);
ret = -EINVAL;
goto unlock_out;
}
if (req->ackxid == hdr->xid)
{
req->ackxid = 0;
if (req_done)
{
*req_done = true;
}
/* Signal that request was received and read by daemon and
* acknowledgment response was received.
*/
nxsem_post(&req->acksem);
}
conn->resp.events = hdr->head.events | USRSOCK_EVENT_REQ_COMPLETE;
ret = handle_response(conn, buffer, len);
unlock_out:
net_unlock();
return ret;
}
/****************************************************************************
* Name: usrsock_handle_message
****************************************************************************/
static ssize_t usrsock_handle_message(FAR const void *buffer, size_t len,
FAR bool *req_done)
{
FAR const struct usrsock_message_common_s *common = buffer;
if (USRSOCK_MESSAGE_IS_EVENT(common->flags))
{
return usrsock_handle_event(buffer, len);
}
if (USRSOCK_MESSAGE_IS_REQ_RESPONSE(common->flags))
{
return usrsock_handle_req_response(buffer, len, req_done);
}
nerr("Unknown message flags %" PRIx8 ".\n", common->flags);
return -EINVAL;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usrsock_response() - handle usrsock request's ack/response
****************************************************************************/
ssize_t usrsock_response(FAR const char *buffer, size_t len,
FAR bool *req_done)
{
FAR struct usrsock_req_s *req = &g_usrsock_req;
FAR struct usrsock_conn_s *conn;
size_t origlen = len;
int ret = 0;
if (!req->datain_conn)
{
/* Start of message, buffer length should be at least size of common
* message header.
*/
if (len < sizeof(struct usrsock_message_common_s))
{
nerr("message too short, %zu < %zu.\n", len,
sizeof(struct usrsock_message_common_s));
return -EINVAL;
}
/* Handle message. */
ret = usrsock_handle_message(buffer, len, req_done);
if (ret >= 0)
{
buffer += ret;
len -= ret;
ret = origlen - len;
}
}
if (req->datain_conn)
{
conn = req->datain_conn;
/* Copy data from user-space. */
if (len != 0)
{
ret = usrsock_iovec_put(conn->resp.datain.iov,
conn->resp.datain.iovcnt,
conn->resp.datain.pos, buffer, len);
if (ret < 0)
{
/* Tried writing beyond buffer. */
conn->resp.result = ret;
conn->resp.datain.pos = conn->resp.datain.total;
}
else
{
conn->resp.datain.pos += ret;
buffer += ret;
len -= ret;
ret = origlen - len;
}
}
if (conn->resp.datain.pos == conn->resp.datain.total)
{
req->datain_conn = NULL;
/* Done with data response. */
usrsock_event(conn);
}
}
return ret;
}
/****************************************************************************
* Name: usrsock_iovec_get() - copy from iovec to buffer.
****************************************************************************/
ssize_t usrsock_iovec_get(FAR void *dst, size_t dstlen,
FAR const struct iovec *iov, int iovcnt,
size_t pos, FAR bool *done)
{
return usrsock_iovec_do(dst, dstlen, (FAR struct iovec *)iov, iovcnt,
pos, true, done);
}
/****************************************************************************
* Name: usrsock_iovec_put() - copy to iovec from buffer.
****************************************************************************/
ssize_t usrsock_iovec_put(FAR struct iovec *iov, int iovcnt, size_t pos,
FAR const void *src, size_t srclen)
{
return usrsock_iovec_do((FAR void *)src, srclen, iov, iovcnt,
pos, false, NULL);
}
/****************************************************************************
* Name: usrsock_do_request() - finish usrsock's request
****************************************************************************/
int usrsock_do_request(FAR struct usrsock_conn_s *conn,
FAR struct iovec *iov, unsigned int iovcnt)
{
FAR struct usrsock_request_common_s *req_head = NULL;
FAR struct usrsock_req_s *req = &g_usrsock_req;
int ret;
/* Get exchange id. */
req_head = iov[0].iov_base;
/* Set outstanding request for daemon to handle. */
net_mutex_lock(&req->lock);
if (++req->newxid == 0)
{
++req->newxid;
}
req_head->xid = req->newxid;
/* Prepare connection for response. */
conn->resp.xid = req_head->xid;
conn->resp.result = -EACCES;
req->ackxid = req_head->xid;
ret = usrsock_request(iov, iovcnt);
if (ret >= 0)
{
/* Wait ack for request. */
++req->nbusy; /* net_lock held. */
net_sem_wait_uninterruptible(&req->acksem);
--req->nbusy; /* net_lock held. */
}
else
{
nerr("error: usrsock request failed with %d\n", ret);
}
/* Free request line for next command. */
nxmutex_unlock(&req->lock);
return ret;
}
/****************************************************************************
* Name: usrsock_abort() - abort all usrsock's operations
****************************************************************************/
void usrsock_abort(void)
{
FAR struct usrsock_req_s *req = &g_usrsock_req;
FAR struct usrsock_conn_s *conn = NULL;
int ret;
net_lock();
/* Set active usrsock sockets to aborted state. */
while ((conn = usrsock_nextconn(conn)) != NULL)
{
conn->resp.inprogress = false;
conn->resp.xid = 0;
conn->resp.events = USRSOCK_EVENT_ABORT;
usrsock_event(conn);
}
do
{
/* Give other threads short time window to complete recently completed
* requests.
*/
ret = net_mutex_timedlock(&req->lock, 10);
if (ret < 0)
{
if (ret != -ETIMEDOUT && ret != -EINTR)
{
ninfo("net_sem_timedwait errno: %d\n", ret);
DEBUGASSERT(false);
}
}
else
{
nxmutex_unlock(&req->lock);
}
/* Wake-up pending requests. */
if (req->nbusy == 0)
{
break;
}
nxsem_post(&req->acksem);
}
while (true);
net_unlock();
}
#endif /* CONFIG_NET && CONFIG_NET_USRSOCK */