| /**************************************************************************** |
| * net/tcp/tcp_netpoll.c |
| * |
| * 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> |
| |
| #include <stdint.h> |
| #include <assert.h> |
| #include <poll.h> |
| #include <debug.h> |
| |
| #include <nuttx/net/net.h> |
| #include <nuttx/net/tcp.h> |
| #include <nuttx/semaphore.h> |
| |
| #include "devif/devif.h" |
| #include "netdev/netdev.h" |
| #include "socket/socket.h" |
| #include "inet/inet.h" |
| #include "tcp/tcp.h" |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: tcp_poll_eventhandler |
| * |
| * Description: |
| * This function is called to perform the actual TCP receive operation via |
| * the device interface layer. |
| * |
| * Input Parameters: |
| * dev The structure of the network driver that caused the event |
| * pvpriv An instance of struct tcp_poll_s cast to void* |
| * flags Set of events describing why the callback was invoked |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * The network is locked |
| * |
| ****************************************************************************/ |
| |
| static uint16_t tcp_poll_eventhandler(FAR struct net_driver_s *dev, |
| FAR void *pvpriv, uint16_t flags) |
| { |
| FAR struct tcp_poll_s *info = pvpriv; |
| |
| ninfo("flags: %04x\n", flags); |
| |
| DEBUGASSERT(info == NULL || (info->conn != NULL && info->fds != NULL)); |
| |
| /* 'priv' might be null in some race conditions (?) */ |
| |
| if (info != NULL) |
| { |
| pollevent_t eventset = 0; |
| |
| /* Check for data or connection availability events. */ |
| |
| if ((flags & (TCP_NEWDATA | TCP_BACKLOG)) != 0) |
| { |
| eventset |= POLLIN; |
| } |
| |
| /* Non-blocking connection */ |
| |
| if ((flags & TCP_CONNECTED) != 0) |
| { |
| eventset |= POLLOUT; |
| } |
| |
| /* Check for a loss of connection events. */ |
| |
| if ((flags & TCP_DISCONN_EVENTS) != 0) |
| { |
| #ifdef CONFIG_NET_SOCKOPTS |
| int reason; |
| |
| /* TCP_TIMEDOUT: Connection aborted due to too many |
| * retransmissions. |
| */ |
| |
| if ((flags & TCP_TIMEDOUT) != 0) |
| { |
| /* Indicate that the connection timedout?) */ |
| |
| reason = ETIMEDOUT; |
| } |
| |
| else if ((flags & NETDEV_DOWN) != 0) |
| { |
| /* The network device went down. Indicate that the remote host |
| * is unreachable. |
| */ |
| |
| reason = ENETUNREACH; |
| } |
| |
| /* TCP_CLOSE: The remote host has closed the connection |
| * TCP_ABORT: The remote host has aborted the connection |
| */ |
| |
| else |
| { |
| /* Indicate that remote host refused the connection */ |
| |
| reason = ECONNREFUSED; |
| } |
| |
| _SO_CONN_SETERRNO(info->conn, reason); |
| #endif |
| |
| /* Mark that the connection has been lost */ |
| |
| tcp_lost_connection(info->conn, info->cb, flags); |
| eventset |= (POLLERR | POLLHUP); |
| } |
| |
| /* A poll is a sign that we are free to send data. */ |
| |
| /* Wake up poll() speculatively on TCP_ACKDATA. |
| * Note: our event handler is usually executed before |
| * psock_send_eventhandler, which might free IOBs/WRBs on TCP_ACKDATA. |
| * Revisit: consider some kind of priority for devif callback to allow |
| * this callback to be inserted after psock_send_eventhandler. |
| */ |
| |
| else if (psock_tcp_cansend(info->conn) >= 0 |
| #if defined(CONFIG_NET_TCP_WRITE_BUFFERS) |
| || (flags & TCP_ACKDATA) != 0 |
| #endif |
| ) |
| { |
| eventset |= POLLOUT; |
| } |
| |
| /* Awaken the caller of poll() if requested event occurred. */ |
| |
| poll_notify(&info->fds, 1, eventset); |
| |
| if (info->fds->revents != 0) |
| { |
| /* Stop further callbacks */ |
| |
| info->cb->flags = 0; |
| info->cb->priv = NULL; |
| info->cb->event = NULL; |
| } |
| } |
| |
| return flags; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: tcp_pollsetup |
| * |
| * Description: |
| * Setup to monitor events on one TCP/IP socket |
| * |
| * Input Parameters: |
| * psock - The TCP/IP socket of interest |
| * fds - The structure describing the events to be monitored, OR NULL if |
| * this is a request to stop monitoring events. |
| * |
| * Returned Value: |
| * 0: Success; Negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int tcp_pollsetup(FAR struct socket *psock, FAR struct pollfd *fds) |
| { |
| FAR struct tcp_conn_s *conn; |
| FAR struct tcp_poll_s *info; |
| FAR struct devif_callback_s *cb; |
| pollevent_t eventset = 0; |
| bool nonblock_conn; |
| int ret = OK; |
| |
| /* Some of the following must be atomic */ |
| |
| net_lock(); |
| |
| conn = psock->s_conn; |
| |
| /* Sanity check */ |
| |
| if (!conn || !fds) |
| { |
| ret = -EINVAL; |
| goto errout_with_lock; |
| } |
| |
| /* Non-blocking connection ? */ |
| |
| nonblock_conn = ((conn->tcpstateflags == TCP_ALLOCATED || |
| conn->tcpstateflags == TCP_SYN_SENT) && |
| _SS_ISNONBLOCK(conn->sconn.s_flags)); |
| |
| /* Find a container to hold the poll information */ |
| |
| info = conn->pollinfo; |
| while (info->conn != NULL) |
| { |
| if (++info >= &conn->pollinfo[CONFIG_NET_TCP_NPOLLWAITERS]) |
| { |
| DEBUGPANIC(); |
| ret = -ENOMEM; |
| goto errout_with_lock; |
| } |
| } |
| |
| /* Allocate a TCP/IP callback structure */ |
| |
| cb = tcp_callback_alloc(conn); |
| if (!cb) |
| { |
| ret = -EBUSY; |
| goto errout_with_lock; |
| } |
| |
| /* Initialize the poll info container */ |
| |
| info->conn = conn; |
| info->fds = fds; |
| info->cb = cb; |
| |
| /* Initialize the callback structure. Save the reference to the info |
| * structure as callback private data so that it will be available during |
| * callback processing. |
| */ |
| |
| cb->flags = TCP_DISCONN_EVENTS; |
| cb->priv = info; |
| cb->event = tcp_poll_eventhandler; |
| |
| if ((fds->events & POLLOUT) != 0) |
| { |
| cb->flags |= TCP_POLL; |
| #if defined(CONFIG_NET_TCP_WRITE_BUFFERS) |
| cb->flags |= TCP_ACKDATA; |
| #endif |
| |
| /* Monitor the connected event */ |
| |
| if (nonblock_conn) |
| { |
| cb->flags |= TCP_CONNECTED; |
| } |
| } |
| |
| if ((fds->events & POLLIN) != 0) |
| { |
| cb->flags |= TCP_NEWDATA | TCP_BACKLOG; |
| } |
| |
| /* Save the reference in the poll info structure as fds private as well |
| * for use during poll teardown as well. |
| */ |
| |
| fds->priv = info; |
| |
| /* Check for read data or backlogged connection availability now */ |
| |
| if (conn->readahead != NULL || tcp_backlogpending(conn)) |
| { |
| /* Normal data may be read without blocking. */ |
| |
| eventset |= POLLRDNORM; |
| } |
| |
| /* Check for a loss of connection events. We need to be careful here. |
| * There are four possibilities: |
| * |
| * 1) The socket is connected and we are waiting for data availability |
| * events. |
| * |
| * __SS_ISCONNECTED(f) == true |
| * __SS_ISLISTENING(f) == false |
| * __SS_ISCLOSED(f) == false |
| * |
| * Action: Wait for data availability events |
| * |
| * 2) This is a listener socket that was never connected and we are |
| * waiting for connection events. |
| * |
| * __SS_ISCONNECTED(f) == false |
| * __SS_ISLISTENING(f) == true |
| * __SS_ISCLOSED(f) == false |
| * |
| * Action: Wait for connection events |
| * |
| * 3) This socket was previously connected, but the peer has gracefully |
| * closed the connection. |
| * |
| * __SS_ISCONNECTED(f) == false |
| * __SS_ISLISTENING(f) == false |
| * __SS_ISCLOSED(f) == true |
| * |
| * Action: Return with POLLHUP|POLLERR events |
| * |
| * 4) This socket was previously connected, but we lost the connection |
| * due to some exceptional event. |
| * |
| * __SS_ISCONNECTED(f) == false |
| * __SS_ISLISTENING(f) == false |
| * __SS_ISCLOSED(f) == false |
| * |
| * Action: Return with POLLHUP|POLLERR events |
| */ |
| |
| if (!nonblock_conn && !_SS_ISCONNECTED(conn->sconn.s_flags) && |
| !_SS_ISLISTENING(conn->sconn.s_flags)) |
| { |
| /* We were previously connected but lost the connection either due |
| * to a graceful shutdown by the remote peer or because of some |
| * exceptional event. |
| */ |
| |
| _SO_CONN_SETERRNO(conn, ENOTCONN); |
| eventset |= POLLERR | POLLHUP; |
| } |
| else if (_SS_ISCONNECTED(conn->sconn.s_flags) && |
| psock_tcp_cansend(conn) >= 0) |
| { |
| eventset |= POLLWRNORM; |
| } |
| |
| /* Check if any requested events are already in effect */ |
| |
| poll_notify(&fds, 1, eventset); |
| |
| errout_with_lock: |
| net_unlock(); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: tcp_pollteardown |
| * |
| * Description: |
| * Teardown monitoring of events on an TCP/IP socket |
| * |
| * Input Parameters: |
| * psock - The TCP/IP socket of interest |
| * fds - The structure describing the events to be monitored, OR NULL if |
| * this is a request to stop monitoring events. |
| * |
| * Returned Value: |
| * 0: Success; Negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int tcp_pollteardown(FAR struct socket *psock, FAR struct pollfd *fds) |
| { |
| FAR struct tcp_conn_s *conn; |
| FAR struct tcp_poll_s *info; |
| |
| /* Some of the following must be atomic */ |
| |
| net_lock(); |
| |
| conn = psock->s_conn; |
| |
| /* Sanity check */ |
| |
| if (!conn || !fds->priv) |
| { |
| net_unlock(); |
| return -EINVAL; |
| } |
| |
| /* Recover the socket descriptor poll state info from the poll structure */ |
| |
| info = (FAR struct tcp_poll_s *)fds->priv; |
| DEBUGASSERT(info->fds != NULL && info->cb != NULL); |
| if (info != NULL) |
| { |
| /* Release the callback */ |
| |
| tcp_callback_free(conn, info->cb); |
| |
| /* Release the poll/select data slot */ |
| |
| info->fds->priv = NULL; |
| |
| /* Then free the poll info container */ |
| |
| info->conn = NULL; |
| } |
| |
| net_unlock(); |
| |
| return OK; |
| } |