1278 lines
31 KiB
C
1278 lines
31 KiB
C
/****************************************************************************
|
|
* net/usrsock/usrsock_dev.c
|
|
*
|
|
* Copyright (C) 2015-2017 Haltian Ltd. All rights reserved.
|
|
* Author: Jussi Kivilinna <jussi.kivilinna@haltian.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 <poll.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
|
|
#include <arch/irq.h>
|
|
|
|
#include <nuttx/random.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/net/net.h>
|
|
#include <nuttx/net/usrsock.h>
|
|
|
|
#include "devif/devif.h"
|
|
#include "usrsock/usrsock.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_NET_USRSOCKDEV_NPOLLWAITERS
|
|
# define CONFIG_NET_USRSOCKDEV_NPOLLWAITERS 1
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct usrsockdev_s
|
|
{
|
|
sem_t devsem; /* Lock for device node */
|
|
uint8_t ocount; /* The number of times the device has been opened */
|
|
|
|
struct
|
|
{
|
|
FAR const struct iovec *iov; /* Pending request buffers */
|
|
int iovcnt; /* Number of request buffers */
|
|
size_t pos; /* Reader position on request buffer */
|
|
sem_t sem; /* Request semaphore (only one outstanding
|
|
* request) */
|
|
sem_t acksem; /* Request acknowledgment notification */
|
|
uint8_t ack_xid; /* Exchange id for which waiting ack */
|
|
uint16_t nbusy; /* Number of requests blocked from different
|
|
* threads */
|
|
} req;
|
|
|
|
FAR struct usrsock_conn_s *datain_conn; /* Connection instance to receive
|
|
* data buffers. */
|
|
struct pollfd *pollfds[CONFIG_NET_USRSOCKDEV_NPOLLWAITERS];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Character driver methods */
|
|
|
|
static ssize_t usrsockdev_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t len);
|
|
|
|
static ssize_t usrsockdev_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t len);
|
|
|
|
static off_t usrsockdev_seek(FAR struct file *filep, off_t offset,
|
|
int whence);
|
|
|
|
static int usrsockdev_open(FAR struct file *filep);
|
|
|
|
static int usrsockdev_close(FAR struct file *filep);
|
|
|
|
static int usrsockdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_usrsockdevops =
|
|
{
|
|
usrsockdev_open, /* open */
|
|
usrsockdev_close, /* close */
|
|
usrsockdev_read, /* read */
|
|
usrsockdev_write, /* write */
|
|
usrsockdev_seek, /* seek */
|
|
NULL, /* ioctl */
|
|
usrsockdev_poll /* poll */
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, NULL /* unlink */
|
|
#endif
|
|
};
|
|
|
|
static struct usrsockdev_s g_usrsockdev;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: iovec_do() - copy to/from iovec from/to buffer.
|
|
****************************************************************************/
|
|
|
|
static ssize_t iovec_do(FAR void *srcdst, size_t srcdstlen,
|
|
FAR struct iovec *iov, int iovcnt, size_t pos,
|
|
bool from_iov)
|
|
{
|
|
ssize_t total;
|
|
size_t srclen;
|
|
FAR uint8_t *ioout = srcdst;
|
|
FAR uint8_t *iovbuf;
|
|
|
|
/* 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. */
|
|
|
|
return -1;
|
|
}
|
|
|
|
iovbuf = iov->iov_base;
|
|
srclen = iov->iov_len;
|
|
iovbuf += pos;
|
|
srclen -= pos;
|
|
iov++;
|
|
iovcnt--;
|
|
total = 0;
|
|
|
|
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--;
|
|
}
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: iovec_get() - copy from iovec to buffer.
|
|
****************************************************************************/
|
|
|
|
static ssize_t iovec_get(FAR void *dst, size_t dstlen,
|
|
FAR const struct iovec *iov, int iovcnt, size_t pos)
|
|
{
|
|
return iovec_do(dst, dstlen, (FAR struct iovec *)iov, iovcnt, pos, true);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: iovec_put() - copy to iovec from buffer.
|
|
****************************************************************************/
|
|
|
|
static ssize_t iovec_put(FAR struct iovec *iov, int iovcnt, size_t pos,
|
|
FAR const void *src, size_t srclen)
|
|
{
|
|
return iovec_do((FAR void *)src, srclen, iov, iovcnt, pos, false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_get_xid()
|
|
****************************************************************************/
|
|
|
|
static uint8_t usrsockdev_get_xid(FAR struct usrsock_conn_s *conn)
|
|
{
|
|
int conn_idx;
|
|
|
|
#if CONFIG_NET_USRSOCK_CONNS > 254
|
|
# error "CONFIG_NET_USRSOCK_CONNS too large (over 254)"
|
|
#endif
|
|
|
|
/* Each connection can one only one request/response pending. So map
|
|
* connection structure index to xid value.
|
|
*/
|
|
|
|
conn_idx = usrsock_connidx(conn);
|
|
|
|
DEBUGASSERT(1 <= conn_idx + 1);
|
|
DEBUGASSERT(conn_idx + 1 <= UINT8_MAX);
|
|
|
|
return conn_idx + 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_semtake() and usrsockdev_semgive()
|
|
*
|
|
* Description:
|
|
* Take/give semaphore
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usrsockdev_semtake(FAR sem_t *sem)
|
|
{
|
|
return nxsem_wait_uninterruptible(sem);
|
|
}
|
|
|
|
static void usrsockdev_semgive(FAR sem_t *sem)
|
|
{
|
|
nxsem_post(sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_is_opened
|
|
****************************************************************************/
|
|
|
|
static bool usrsockdev_is_opened(FAR struct usrsockdev_s *dev)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (dev->ocount == 0)
|
|
{
|
|
ret = false; /* No usrsock daemon running. */
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_pollnotify
|
|
****************************************************************************/
|
|
|
|
static void usrsockdev_pollnotify(FAR struct usrsockdev_s *dev,
|
|
pollevent_t eventset)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(dev->pollfds); i++)
|
|
{
|
|
struct pollfd *fds = dev->pollfds[i];
|
|
if (fds)
|
|
{
|
|
fds->revents |= (fds->events & eventset);
|
|
if (fds->revents != 0)
|
|
{
|
|
ninfo("Report events: %02x\n", fds->revents);
|
|
nxsem_post(fds->sem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t len)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsockdev_s *dev;
|
|
int ret;
|
|
|
|
if (len == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
ret = usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
net_lock();
|
|
|
|
/* Is request available? */
|
|
|
|
if (dev->req.iov)
|
|
{
|
|
ssize_t rlen;
|
|
|
|
/* Copy request to user-space. */
|
|
|
|
rlen = iovec_get(buffer, len, dev->req.iov, dev->req.iovcnt,
|
|
dev->req.pos);
|
|
if (rlen < 0)
|
|
{
|
|
/* Tried reading beyond buffer. */
|
|
|
|
len = 0;
|
|
}
|
|
else
|
|
{
|
|
dev->req.pos += rlen;
|
|
len = rlen;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
len = 0;
|
|
}
|
|
|
|
net_unlock();
|
|
usrsockdev_semgive(&dev->devsem);
|
|
|
|
return len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_seek
|
|
****************************************************************************/
|
|
|
|
static off_t usrsockdev_seek(FAR struct file *filep, off_t offset,
|
|
int whence)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsockdev_s *dev;
|
|
off_t pos;
|
|
int ret;
|
|
|
|
if (whence != SEEK_CUR && whence != SEEK_SET)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
ret = usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
net_lock();
|
|
|
|
/* Is request available? */
|
|
|
|
if (dev->req.iov)
|
|
{
|
|
ssize_t rlen;
|
|
|
|
if (whence == SEEK_CUR)
|
|
{
|
|
pos = dev->req.pos + offset;
|
|
}
|
|
else
|
|
{
|
|
pos = offset;
|
|
}
|
|
|
|
/* Copy request to user-space. */
|
|
|
|
rlen = iovec_get(NULL, 0, dev->req.iov, dev->req.iovcnt, pos);
|
|
if (rlen < 0)
|
|
{
|
|
/* Tried seek beyond buffer. */
|
|
|
|
pos = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
dev->req.pos = pos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pos = 0;
|
|
}
|
|
|
|
net_unlock();
|
|
usrsockdev_semgive(&dev->devsem);
|
|
|
|
return pos;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_handle_event
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_handle_event(FAR struct usrsockdev_s *dev,
|
|
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))
|
|
{
|
|
nwarn("message too short, %d < %d.\n", len, sizeof(*hdr));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get corresponding usrsock connection. */
|
|
|
|
conn = usrsock_active(hdr->usockid);
|
|
if (!conn)
|
|
{
|
|
nwarn("no active connection for usockid=%d.\n", hdr->usockid);
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
#ifdef CONFIG_DEV_RANDOM
|
|
/* Add randomness. */
|
|
|
|
add_sw_randomness((hdr->events << 16) - hdr->usockid);
|
|
#endif
|
|
|
|
/* Handle event. */
|
|
|
|
ret = usrsock_event(conn,
|
|
hdr->events & ~USRSOCK_EVENT_INTERNAL_MASK);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
len = sizeof(*hdr);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nwarn("Unknown event type: %d\n", common->msgid);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_handle_response
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_handle_response(FAR struct usrsockdev_s *dev,
|
|
FAR struct usrsock_conn_s *conn,
|
|
FAR const void *buffer)
|
|
{
|
|
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;
|
|
}
|
|
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, USRSOCK_EVENT_REQ_COMPLETE);
|
|
}
|
|
|
|
return sizeof(*hdr);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_handle_datareq_response
|
|
****************************************************************************/
|
|
|
|
static ssize_t
|
|
usrsockdev_handle_datareq_response(FAR struct usrsockdev_s *dev,
|
|
FAR struct usrsock_conn_s *conn,
|
|
FAR const void *buffer)
|
|
{
|
|
FAR const struct usrsock_message_datareq_ack_s *datahdr = buffer;
|
|
FAR const struct usrsock_message_req_ack_s *hdr = &datahdr->reqack;
|
|
int num_inbufs;
|
|
int iovpos;
|
|
ssize_t ret;
|
|
|
|
if (USRSOCK_MESSAGE_REQ_IN_PROGRESS(hdr->head.flags))
|
|
{
|
|
if (datahdr->reqack.result > 0)
|
|
{
|
|
ninfo("error: request in progress, and result > 0.\n");
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
else if (datahdr->valuelen > 0)
|
|
{
|
|
ninfo("error: request in progress, and valuelen > 0.\n");
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* In-progress response is acknowledgment that response was
|
|
* received.
|
|
*/
|
|
|
|
conn->resp.inprogress = true;
|
|
|
|
ret = sizeof(*datahdr);
|
|
goto unlock_out;
|
|
}
|
|
|
|
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");
|
|
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* Done with request/response. */
|
|
|
|
usrsock_event(conn, USRSOCK_EVENT_REQ_COMPLETE);
|
|
|
|
ret = sizeof(*datahdr);
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* 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: %d).\n",
|
|
iovpos,
|
|
datahdr->valuelen,
|
|
conn->resp.datain.iov[iovpos].iov_len);
|
|
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* 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: %d).\n",
|
|
iovpos,
|
|
hdr->result,
|
|
conn->resp.datain.iov[iovpos].iov_len);
|
|
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
dev->datain_conn = conn;
|
|
ret = sizeof(*datahdr);
|
|
|
|
unlock_out:
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_handle_req_response
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_handle_req_response(FAR struct usrsockdev_s *dev,
|
|
FAR const void *buffer,
|
|
size_t len)
|
|
{
|
|
FAR const struct usrsock_message_req_ack_s *hdr = buffer;
|
|
FAR struct usrsock_conn_s *conn;
|
|
unsigned int hdrlen;
|
|
ssize_t ret;
|
|
ssize_t (*handle_response)(FAR struct usrsockdev_s *dev,
|
|
FAR struct usrsock_conn_s *conn,
|
|
FAR const void *buffer);
|
|
|
|
switch (hdr->head.msgid)
|
|
{
|
|
case USRSOCK_MESSAGE_RESPONSE_ACK:
|
|
hdrlen = sizeof(struct usrsock_message_req_ack_s);
|
|
handle_response = &usrsockdev_handle_response;
|
|
break;
|
|
|
|
case USRSOCK_MESSAGE_RESPONSE_DATA_ACK:
|
|
hdrlen = sizeof(struct usrsock_message_datareq_ack_s);
|
|
handle_response = &usrsockdev_handle_datareq_response;
|
|
break;
|
|
|
|
default:
|
|
nwarn("unknown message type: %d, flags: %d, xid: %02x, "
|
|
"result: %" PRId32 "\n",
|
|
hdr->head.msgid, hdr->head.flags, hdr->xid, hdr->result);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (len < hdrlen)
|
|
{
|
|
nwarn("message too short, %d < %d.\n", len, hdrlen);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
net_lock();
|
|
|
|
/* Get corresponding usrsock connection for this transfer */
|
|
|
|
conn = usrsock_nextconn(NULL);
|
|
while (conn)
|
|
{
|
|
if (conn->resp.xid == hdr->xid)
|
|
break;
|
|
|
|
conn = usrsock_nextconn(conn);
|
|
}
|
|
|
|
if (!conn)
|
|
{
|
|
/* No connection waiting for this message. */
|
|
|
|
nwarn("Could find connection waiting for response with xid=%d\n",
|
|
hdr->xid);
|
|
|
|
ret = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
|
|
if (dev->req.ack_xid == hdr->xid && dev->req.iov)
|
|
{
|
|
/* Signal that request was received and read by daemon and
|
|
* acknowledgment response was received.
|
|
*/
|
|
|
|
dev->req.iov = NULL;
|
|
|
|
nxsem_post(&dev->req.acksem);
|
|
}
|
|
|
|
ret = handle_response(dev, conn, buffer);
|
|
|
|
unlock_out:
|
|
net_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_handle_message
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_handle_message(FAR struct usrsockdev_s *dev,
|
|
FAR const void *buffer,
|
|
size_t len)
|
|
{
|
|
FAR const struct usrsock_message_common_s *common = buffer;
|
|
|
|
if (USRSOCK_MESSAGE_IS_EVENT(common->flags))
|
|
{
|
|
return usrsockdev_handle_event(dev, buffer, len);
|
|
}
|
|
|
|
if (USRSOCK_MESSAGE_IS_REQ_RESPONSE(common->flags))
|
|
{
|
|
return usrsockdev_handle_req_response(dev, buffer, len);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t usrsockdev_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t len)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsock_conn_s *conn;
|
|
FAR struct usrsockdev_s *dev;
|
|
size_t origlen = len;
|
|
ssize_t ret = 0;
|
|
|
|
if (len == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
ret = (ssize_t)usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (!dev->datain_conn)
|
|
{
|
|
/* Start of message, buffer length should be at least size of common
|
|
* message header.
|
|
*/
|
|
|
|
if (len < sizeof(struct usrsock_message_common_s))
|
|
{
|
|
nwarn("message too short, %d < %d.\n", len,
|
|
sizeof(struct usrsock_message_common_s));
|
|
|
|
ret = -EINVAL;
|
|
goto errout;
|
|
}
|
|
|
|
/* Handle message. */
|
|
|
|
ret = usrsockdev_handle_message(dev, buffer, len);
|
|
if (ret >= 0)
|
|
{
|
|
buffer += ret;
|
|
len -= ret;
|
|
ret = origlen - len;
|
|
}
|
|
}
|
|
|
|
/* Data input handling. */
|
|
|
|
if (dev->datain_conn)
|
|
{
|
|
conn = dev->datain_conn;
|
|
|
|
/* Copy data from user-space. */
|
|
|
|
ret = iovec_put(conn->resp.datain.iov, conn->resp.datain.iovcnt,
|
|
conn->resp.datain.pos, buffer, len);
|
|
if (ret < 0)
|
|
{
|
|
/* Tried writing beyond buffer. */
|
|
|
|
ret = -EINVAL;
|
|
conn->resp.result = -EINVAL;
|
|
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)
|
|
{
|
|
dev->datain_conn = NULL;
|
|
|
|
/* Done with data response. */
|
|
|
|
usrsock_event(conn, USRSOCK_EVENT_REQ_COMPLETE);
|
|
}
|
|
}
|
|
|
|
errout:
|
|
usrsockdev_semgive(&dev->devsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_open
|
|
****************************************************************************/
|
|
|
|
static int usrsockdev_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsockdev_s *dev;
|
|
int ret;
|
|
int tmp;
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
ret = usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ninfo("opening /dev/usrsock\n");
|
|
|
|
/* Increment the count of references to the device. */
|
|
|
|
tmp = dev->ocount + 1;
|
|
if (tmp > 1)
|
|
{
|
|
/* Only one reference is allowed. */
|
|
|
|
nwarn("failed to open\n");
|
|
|
|
ret = -EPERM;
|
|
}
|
|
else
|
|
{
|
|
dev->ocount = tmp;
|
|
ret = OK;
|
|
}
|
|
|
|
usrsockdev_semgive(&dev->devsem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_close
|
|
****************************************************************************/
|
|
|
|
static int usrsockdev_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsockdev_s *dev;
|
|
FAR struct usrsock_conn_s *conn;
|
|
int ret;
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
ret = usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ninfo("closing /dev/usrsock\n");
|
|
|
|
/* Set active usrsock sockets to aborted state. */
|
|
|
|
conn = usrsock_nextconn(NULL);
|
|
while (conn)
|
|
{
|
|
net_lock();
|
|
|
|
conn->resp.inprogress = false;
|
|
conn->resp.xid = 0;
|
|
usrsock_event(conn, USRSOCK_EVENT_ABORT);
|
|
|
|
net_unlock();
|
|
|
|
conn = usrsock_nextconn(conn);
|
|
}
|
|
|
|
net_lock();
|
|
|
|
/* Decrement the references to the driver. */
|
|
|
|
dev->ocount--;
|
|
DEBUGASSERT(dev->ocount == 0);
|
|
ret = OK;
|
|
|
|
do
|
|
{
|
|
/* Give other threads short time window to complete recently completed
|
|
* requests.
|
|
*/
|
|
|
|
ret = net_timedwait(&dev->req.sem, 10);
|
|
if (ret < 0)
|
|
{
|
|
if (ret != -ETIMEDOUT && ret != -EINTR)
|
|
{
|
|
ninfo("net_timedwait errno: %d\n", ret);
|
|
DEBUGASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
usrsockdev_semgive(&dev->req.sem);
|
|
}
|
|
|
|
/* Wake-up pending requests. */
|
|
|
|
if (dev->req.nbusy == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
dev->req.iov = NULL;
|
|
nxsem_post(&dev->req.acksem);
|
|
}
|
|
while (true);
|
|
|
|
net_unlock();
|
|
|
|
/* Check if request line is active */
|
|
|
|
if (dev->req.iov != NULL)
|
|
{
|
|
dev->req.iov = NULL;
|
|
}
|
|
|
|
usrsockdev_semgive(&dev->devsem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_poll
|
|
****************************************************************************/
|
|
|
|
static int usrsockdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct usrsockdev_s *dev;
|
|
pollevent_t eventset;
|
|
int ret;
|
|
int i;
|
|
|
|
DEBUGASSERT(inode);
|
|
|
|
dev = inode->i_private;
|
|
|
|
DEBUGASSERT(dev);
|
|
|
|
/* Some sanity checking */
|
|
|
|
if (!dev || !fds)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Are we setting up the poll? Or tearing it down? */
|
|
|
|
ret = usrsockdev_semtake(&dev->devsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
net_lock();
|
|
if (setup)
|
|
{
|
|
/* This is a request to set up the poll. Find an available
|
|
* slot for the poll structure reference
|
|
*/
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dev->pollfds); i++)
|
|
{
|
|
/* Find an available slot */
|
|
|
|
if (!dev->pollfds[i])
|
|
{
|
|
/* Bind the poll structure and this slot */
|
|
|
|
dev->pollfds[i] = fds;
|
|
fds->priv = &dev->pollfds[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= ARRAY_SIZE(dev->pollfds))
|
|
{
|
|
fds->priv = NULL;
|
|
ret = -EBUSY;
|
|
goto errout;
|
|
}
|
|
|
|
/* Should immediately notify on any of the requested events? */
|
|
|
|
eventset = 0;
|
|
|
|
/* Notify the POLLIN event if pending request. */
|
|
|
|
if (dev->req.iov != NULL &&
|
|
!(iovec_get(NULL, 0, dev->req.iov,
|
|
dev->req.iovcnt, dev->req.pos) < 0))
|
|
{
|
|
eventset |= POLLIN;
|
|
}
|
|
|
|
if (eventset)
|
|
{
|
|
usrsockdev_pollnotify(dev, eventset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* This is a request to tear down the poll. */
|
|
|
|
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
|
|
|
|
if (!slot)
|
|
{
|
|
ret = -EIO;
|
|
goto errout;
|
|
}
|
|
|
|
/* Remove all memory of the poll setup */
|
|
|
|
*slot = NULL;
|
|
fds->priv = NULL;
|
|
}
|
|
|
|
errout:
|
|
net_unlock();
|
|
usrsockdev_semgive(&dev->devsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_do_request
|
|
****************************************************************************/
|
|
|
|
int usrsockdev_do_request(FAR struct usrsock_conn_s *conn,
|
|
FAR struct iovec *iov, unsigned int iovcnt)
|
|
{
|
|
FAR struct usrsockdev_s *dev = conn->dev;
|
|
FAR struct usrsock_request_common_s *req_head = iov[0].iov_base;
|
|
|
|
if (!dev)
|
|
{
|
|
/* Setup conn for new usrsock device. */
|
|
|
|
dev = &g_usrsockdev;
|
|
conn->dev = dev;
|
|
}
|
|
|
|
if (!usrsockdev_is_opened(dev))
|
|
{
|
|
ninfo("usockid=%d; daemon has closed /dev/usrsock.\n", conn->usockid);
|
|
|
|
return -ENETDOWN;
|
|
}
|
|
|
|
/* Get exchange id. */
|
|
|
|
req_head->xid = usrsockdev_get_xid(conn);
|
|
|
|
/* Prepare connection for response. */
|
|
|
|
conn->resp.xid = req_head->xid;
|
|
conn->resp.result = -EACCES;
|
|
|
|
++dev->req.nbusy; /* net_lock held. */
|
|
|
|
/* Set outstanding request for daemon to handle. */
|
|
|
|
net_lockedwait_uninterruptible(&dev->req.sem);
|
|
|
|
if (usrsockdev_is_opened(dev))
|
|
{
|
|
DEBUGASSERT(dev->req.iov == NULL);
|
|
dev->req.ack_xid = req_head->xid;
|
|
dev->req.iov = iov;
|
|
dev->req.pos = 0;
|
|
dev->req.iovcnt = iovcnt;
|
|
|
|
/* Notify daemon of new request. */
|
|
|
|
usrsockdev_pollnotify(dev, POLLIN);
|
|
|
|
/* Wait ack for request. */
|
|
|
|
net_lockedwait_uninterruptible(&dev->req.acksem);
|
|
}
|
|
else
|
|
{
|
|
ninfo("usockid=%d; daemon abruptly closed /dev/usrsock.\n",
|
|
conn->usockid);
|
|
}
|
|
|
|
/* Free request line for next command. */
|
|
|
|
usrsockdev_semgive(&dev->req.sem);
|
|
|
|
--dev->req.nbusy; /* net_lock held. */
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usrsockdev_register
|
|
*
|
|
* Description:
|
|
* Register /dev/usrsock
|
|
*
|
|
****************************************************************************/
|
|
|
|
void usrsockdev_register(void)
|
|
{
|
|
/* Initialize device private structure. */
|
|
|
|
g_usrsockdev.ocount = 0;
|
|
g_usrsockdev.req.nbusy = 0;
|
|
nxsem_init(&g_usrsockdev.devsem, 0, 1);
|
|
nxsem_init(&g_usrsockdev.req.sem, 0, 1);
|
|
nxsem_init(&g_usrsockdev.req.acksem, 0, 0);
|
|
nxsem_set_protocol(&g_usrsockdev.req.acksem, SEM_PRIO_NONE);
|
|
|
|
register_driver("/dev/usrsock", &g_usrsockdevops, 0666,
|
|
&g_usrsockdev);
|
|
}
|
|
|
|
#endif /* CONFIG_NET && CONFIG_NET_USRSOCK */
|