| /**************************************************************************** |
| * 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 transaction 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 */ |