| /**************************************************************************** |
| * net/tcp/tcp_send_buffered.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> |
| |
| #if defined(CONFIG_NET) && defined(CONFIG_NET_TCP) && \ |
| defined(CONFIG_NET_TCP_WRITE_BUFFERS) |
| |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_NET_TCP_WRBUFFER_DEBUG) |
| /* Force debug output (from this file only) */ |
| |
| # undef CONFIG_DEBUG_NET |
| # define CONFIG_DEBUG_NET 1 |
| #endif |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <arch/irq.h> |
| #include <nuttx/net/net.h> |
| #include <nuttx/mm/iob.h> |
| #include <nuttx/net/netdev.h> |
| #include <nuttx/net/tcp.h> |
| #include <nuttx/net/net.h> |
| |
| #include "netdev/netdev.h" |
| #include "devif/devif.h" |
| #include "socket/socket.h" |
| #include "inet/inet.h" |
| #include "arp/arp.h" |
| #include "icmpv6/icmpv6.h" |
| #include "neighbor/neighbor.h" |
| #include "route/route.h" |
| #include "utils/utils.h" |
| #include "tcp/tcp.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* If both IPv4 and IPv6 support are both enabled, then we will need to build |
| * in some additional domain selection support. |
| */ |
| |
| #if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6) |
| # define NEED_IPDOMAIN_SUPPORT 1 |
| #endif |
| |
| /* Debug */ |
| |
| #ifdef CONFIG_NET_TCP_WRBUFFER_DUMP |
| # define BUF_DUMP(msg,buf,len) lib_dumpbuffer(msg,buf,len) |
| #else |
| # define BUF_DUMP(msg,buf,len) |
| # undef TCP_WBDUMP |
| # define TCP_WBDUMP(msg,wrb,len,offset) |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: psock_insert_segment |
| * |
| * Description: |
| * Insert a new segment in a write buffer queue, keep the segment queue in |
| * ascending order of sequence number. |
| * |
| * Input Parameters: |
| * wrb The segment to be inserted |
| * q The write buffer queue in which to insert the segment |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * The network is locked |
| * |
| ****************************************************************************/ |
| |
| static void psock_insert_segment(FAR struct tcp_wrbuffer_s *wrb, |
| FAR sq_queue_t *q) |
| { |
| FAR sq_entry_t *entry = (FAR sq_entry_t *)wrb; |
| FAR sq_entry_t *insert = NULL; |
| |
| FAR sq_entry_t *itr; |
| for (itr = sq_peek(q); itr; itr = sq_next(itr)) |
| { |
| FAR struct tcp_wrbuffer_s *wrb0 = (FAR struct tcp_wrbuffer_s *)itr; |
| if (TCP_WBSEQNO(wrb0) < TCP_WBSEQNO(wrb)) |
| { |
| insert = itr; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| if (insert) |
| { |
| sq_addafter(insert, entry, q); |
| } |
| else |
| { |
| sq_addfirst(entry, q); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: psock_writebuffer_notify |
| * |
| * Description: |
| * The TCP connection has been lost. Free all write buffers. |
| * |
| * Input Parameters: |
| * psock The socket structure |
| * conn The connection structure associated with the socket |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_TCP_NOTIFIER |
| static void psock_writebuffer_notify(FAR struct tcp_conn_s *conn) |
| { |
| /* Check if all write buffers have been sent and ACKed */ |
| |
| if (sq_empty(&conn->write_q) && sq_empty(&conn->unacked_q)) |
| { |
| /* Notify any waiters that the write buffers have been drained. */ |
| |
| tcp_writebuffer_signal(conn); |
| } |
| } |
| #else |
| # define psock_writebuffer_notify(conn) |
| #endif |
| |
| static void retransmit_segment(FAR struct tcp_conn_s *conn, |
| FAR struct tcp_wrbuffer_s *wrb) |
| { |
| uint16_t sent; |
| |
| /* Reset the number of bytes sent sent from the write buffer */ |
| |
| sent = TCP_WBSENT(wrb); |
| if (conn->tx_unacked > sent) |
| { |
| conn->tx_unacked -= sent; |
| } |
| else |
| { |
| conn->tx_unacked = 0; |
| } |
| |
| if (conn->sent > sent) |
| { |
| conn->sent -= sent; |
| } |
| else |
| { |
| conn->sent = 0; |
| } |
| |
| TCP_WBSENT(wrb) = 0; |
| ninfo("REXMIT: wrb=%p sent=%u, " |
| "conn tx_unacked=%" PRId32 " sent=%" PRId32 "\n", |
| wrb, TCP_WBSENT(wrb), conn->tx_unacked, conn->sent); |
| |
| /* Free any write buffers that have exceed the retry count */ |
| |
| if (++TCP_WBNRTX(wrb) >= TCP_MAXRTX) |
| { |
| nwarn("WARNING: Expiring wrb=%p nrtx=%u\n", |
| wrb, TCP_WBNRTX(wrb)); |
| |
| /* Return the write buffer to the free list */ |
| |
| tcp_wrbuffer_release(wrb); |
| |
| /* Notify any waiters if the write buffers have been |
| * drained. |
| */ |
| |
| psock_writebuffer_notify(conn); |
| |
| /* NOTE expired is different from un-ACKed, it is designed |
| * to represent the number of segments that have been sent, |
| * retransmitted, and un-ACKed, if expired is not zero, the |
| * connection will be closed. |
| * |
| * field expired can only be updated at TCP_ESTABLISHED |
| * state |
| */ |
| |
| conn->expired++; |
| } |
| else |
| { |
| /* Insert the write buffer into the write_q (in sequence |
| * number order). The retransmission will occur below |
| * when the write buffer with the lowest sequence number |
| * is pulled from the write_q again. |
| */ |
| |
| ninfo("REXMIT: Moving wrb=%p nrtx=%u\n", |
| wrb, TCP_WBNRTX(wrb)); |
| |
| psock_insert_segment(wrb, &conn->write_q); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: psock_lost_connection |
| * |
| * Description: |
| * The TCP connection has been lost. Free all write buffers. |
| * |
| * Input Parameters: |
| * psock The socket structure |
| * conn The connection structure associated with the socket |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void psock_lost_connection(FAR struct tcp_conn_s *conn, |
| bool abort) |
| { |
| FAR sq_entry_t *entry; |
| FAR sq_entry_t *next; |
| |
| /* Do not allow any further callbacks */ |
| |
| if (conn->sndcb != NULL) |
| { |
| conn->sndcb->flags = 0; |
| conn->sndcb->event = NULL; |
| } |
| |
| /* Free all queued write buffers */ |
| |
| for (entry = sq_peek(&conn->unacked_q); entry; entry = next) |
| { |
| next = sq_next(entry); |
| tcp_wrbuffer_release((FAR struct tcp_wrbuffer_s *)entry); |
| } |
| |
| for (entry = sq_peek(&conn->write_q); entry; entry = next) |
| { |
| next = sq_next(entry); |
| tcp_wrbuffer_release((FAR struct tcp_wrbuffer_s *)entry); |
| } |
| |
| #if CONFIG_NET_SEND_BUFSIZE > 0 |
| /* Notify the send buffer available */ |
| |
| tcp_sendbuffer_notify(conn); |
| #endif /* CONFIG_NET_SEND_BUFSIZE */ |
| |
| /* Reset write buffering variables */ |
| |
| sq_init(&conn->unacked_q); |
| sq_init(&conn->write_q); |
| |
| /* Notify any waiters if the write buffers have been drained. */ |
| |
| psock_writebuffer_notify(conn); |
| |
| conn->sent = 0; |
| conn->sndseq_max = 0; |
| |
| /* Force abort the connection. */ |
| |
| if (abort) |
| { |
| conn->tx_unacked = 0; |
| conn->tcpstateflags = TCP_CLOSED; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: parse_sack |
| * |
| * Description: |
| * Parse sack from incoming TCP options |
| * |
| * Input Parameters: |
| * conn - The TCP connection of interest |
| * tcp - Header of tcp structure |
| * segs - Segments edge of sacks |
| * |
| * Returned Value: |
| * Number of sacks |
| * |
| * Assumptions: |
| * The network is locked. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_TCP_SELECTIVE_ACK |
| static int parse_sack(FAR struct tcp_conn_s *conn, FAR struct tcp_hdr_s *tcp, |
| FAR struct tcp_ofoseg_s *segs) |
| { |
| FAR struct tcp_sack_s *sacks; |
| int nsack = 0; |
| uint8_t opt; |
| int i; |
| |
| /* Get the size of the link layer header, |
| * the IP and TCP header |
| */ |
| |
| for (i = 0; i < ((tcp->tcpoffset >> 4) - 5) << 2 ; ) |
| { |
| opt = *(tcp->optdata + i); |
| if (opt == TCP_OPT_END) |
| { |
| /* End of options. */ |
| |
| break; |
| } |
| else if (opt == TCP_OPT_NOOP) |
| { |
| /* NOP option. */ |
| |
| ++i; |
| continue; |
| } |
| else if (opt == TCP_OPT_SACK) |
| { |
| nsack = (*(tcp->optdata + 1 + i) - |
| TCP_OPT_SACK_PERM_LEN) / |
| (sizeof(uint32_t) * 2); |
| sacks = (FAR struct tcp_sack_s *) |
| (tcp->optdata + i + |
| TCP_OPT_SACK_PERM_LEN); |
| |
| for (i = 0; i < nsack; i++) |
| { |
| /* Use the pointer to avoid the error of 4 byte alignment. */ |
| |
| segs[i].left = tcp_getsequence((uint8_t *)&sacks[i]); |
| segs[i].right = tcp_getsequence((uint8_t *)&sacks[i] + 4); |
| } |
| |
| tcp_reorder_ofosegs(nsack, segs); |
| |
| break; |
| } |
| else |
| { |
| /* All other options have a length field, |
| * so that we easily can skip past them. |
| */ |
| |
| if (*(tcp->optdata + 1 + i) == 0) |
| { |
| /* If the length field is zero, |
| * the options are malformed and |
| * we don't process them further. |
| */ |
| |
| break; |
| } |
| } |
| |
| i += *(tcp->optdata + 1 + i); |
| } |
| |
| return nsack; |
| } |
| #endif /* CONFIG_NET_TCP_SELECTIVE_ACK */ |
| |
| /**************************************************************************** |
| * Name: psock_send_eventhandler |
| * |
| * Description: |
| * This function is called to perform the actual send operation when |
| * polled by the lower, device interfacing layer. |
| * |
| * Input Parameters: |
| * dev The structure of the network driver that caused the event |
| * pvpriv An instance of struct tcp_conn_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 psock_send_eventhandler(FAR struct net_driver_s *dev, |
| FAR void *pvpriv, uint16_t flags) |
| { |
| FAR struct tcp_conn_s *conn = pvpriv; |
| #ifdef CONFIG_NET_TCP_SELECTIVE_ACK |
| struct tcp_ofoseg_s ofosegs[TCP_SACK_RANGES_MAX]; |
| uint8_t nsacks = 0; |
| #endif |
| #ifdef CONFIG_NET_TCP_FAST_RETRANSMIT |
| uint32_t rexmitno = 0; |
| #endif |
| |
| /* Get the TCP connection pointer reliably from |
| * the corresponding TCP socket. |
| */ |
| |
| DEBUGASSERT(conn != NULL); |
| |
| /* The TCP socket is connected and, hence, should be bound to a device. |
| * Make sure that the polling device is the one that we are bound to. |
| */ |
| |
| DEBUGASSERT(conn->dev != NULL); |
| if (dev != conn->dev) |
| { |
| return flags; |
| } |
| |
| ninfo("flags: %04x\n", flags); |
| |
| /* The TCP_ACKDATA, TCP_REXMIT and TCP_DISCONN_EVENTS flags are expected to |
| * appear here strictly one at a time, except for the FIN + ACK case. |
| */ |
| |
| DEBUGASSERT((flags & TCP_ACKDATA) == 0 || |
| (flags & TCP_REXMIT) == 0); |
| DEBUGASSERT((flags & TCP_DISCONN_EVENTS) == 0 || |
| (flags & TCP_REXMIT) == 0); |
| |
| /* If this packet contains an acknowledgment, then update the count of |
| * acknowledged bytes. |
| */ |
| |
| if ((flags & TCP_ACKDATA) != 0) |
| { |
| FAR struct tcp_wrbuffer_s *wrb; |
| FAR struct tcp_hdr_s *tcp; |
| FAR sq_entry_t *entry; |
| FAR sq_entry_t *next; |
| uint32_t ackno; |
| |
| /* Get the offset address of the TCP header */ |
| |
| #ifdef CONFIG_NET_IPv4 |
| #ifdef CONFIG_NET_IPv6 |
| if (conn->domain == PF_INET) |
| #endif |
| { |
| tcp = TCPIPv4BUF; |
| } |
| #endif /* CONFIG_NET_IPv4 */ |
| |
| #ifdef CONFIG_NET_IPv6 |
| #ifdef CONFIG_NET_IPv4 |
| else |
| #endif |
| { |
| tcp = TCPIPv6BUF; |
| } |
| #endif /* CONFIG_NET_IPv6 */ |
| |
| /* Get the ACK number from the TCP header */ |
| |
| ackno = tcp_getsequence(tcp->ackno); |
| ninfo("ACK: ackno=%" PRIu32 " flags=%04x\n", ackno, flags); |
| |
| /* Look at every write buffer in the unacked_q. The unacked_q |
| * holds write buffers that have been entirely sent, but which |
| * have not yet been ACKed. |
| */ |
| |
| for (entry = sq_peek(&conn->unacked_q); entry; entry = next) |
| { |
| uint32_t lastseq; |
| |
| /* Check of some or all of this write buffer has been ACKed. */ |
| |
| next = sq_next(entry); |
| wrb = (FAR struct tcp_wrbuffer_s *)entry; |
| |
| /* If the ACKed sequence number is greater than the start |
| * sequence number of the write buffer, then some or all of |
| * the write buffer has been ACKed. |
| */ |
| |
| if (TCP_SEQ_GT(ackno, TCP_WBSEQNO(wrb))) |
| { |
| /* Get the sequence number at the end of the data */ |
| |
| lastseq = TCP_WBSEQNO(wrb) + TCP_WBPKTLEN(wrb); |
| ninfo("ACK: wrb=%p seqno=%" PRIu32 |
| " lastseq=%" PRIu32 " pktlen=%u ackno=%" PRIu32 "\n", |
| wrb, TCP_WBSEQNO(wrb), lastseq, TCP_WBPKTLEN(wrb), |
| ackno); |
| |
| /* Has the entire buffer been ACKed? */ |
| |
| if (TCP_SEQ_GTE(ackno, lastseq)) |
| { |
| ninfo("ACK: wrb=%p Freeing write buffer\n", wrb); |
| |
| /* Yes... Remove the write buffer from ACK waiting queue */ |
| |
| sq_rem(entry, &conn->unacked_q); |
| |
| /* And return the write buffer to the pool of free |
| * buffers |
| */ |
| |
| tcp_wrbuffer_release(wrb); |
| |
| /* Notify any waiters if the write buffers have been |
| * drained. |
| */ |
| |
| psock_writebuffer_notify(conn); |
| } |
| else |
| { |
| unsigned int trimlen; |
| |
| /* No, then just trim the ACKed bytes from the beginning |
| * of the write buffer. This will free up some I/O buffers |
| * that can be reused while are still sending the last |
| * buffers in the chain. |
| */ |
| |
| trimlen = TCP_SEQ_SUB(ackno, TCP_WBSEQNO(wrb)); |
| if (trimlen > TCP_WBSENT(wrb)) |
| { |
| /* More data has been ACKed then we have sent? */ |
| |
| trimlen = TCP_WBSENT(wrb); |
| } |
| |
| ninfo("ACK: wrb=%p trim %u bytes\n", wrb, trimlen); |
| |
| TCP_WBTRIM(wrb, trimlen); |
| TCP_WBSEQNO(wrb) += trimlen; |
| TCP_WBSENT(wrb) -= trimlen; |
| |
| /* Set the new sequence number for what remains */ |
| |
| ninfo("ACK: wrb=%p seqno=%" PRIu32 " pktlen=%u\n", |
| wrb, TCP_WBSEQNO(wrb), TCP_WBPKTLEN(wrb)); |
| } |
| } |
| else if (ackno == TCP_WBSEQNO(wrb)) |
| { |
| #ifdef CONFIG_NET_TCP_CC_NEWRENO |
| if (conn->dupacks >= TCP_FAST_RETRANSMISSION_THRESH) |
| #else |
| /* Reset the duplicate ack counter */ |
| |
| if ((flags & TCP_NEWDATA) != 0) |
| { |
| TCP_WBNACK(wrb) = 0; |
| } |
| |
| /* Duplicate ACK? Retransmit data if need */ |
| |
| if (++TCP_WBNACK(wrb) == TCP_FAST_RETRANSMISSION_THRESH) |
| #endif |
| { |
| #ifdef CONFIG_NET_TCP_SELECTIVE_ACK |
| if ((conn->flags & TCP_SACK) && |
| (tcp->tcpoffset & 0xf0) > 0x50) |
| { |
| /* Parse s-ack from tcp options */ |
| |
| nsacks = parse_sack(conn, tcp, ofosegs); |
| |
| flags |= TCP_REXMIT; |
| } |
| #ifdef CONFIG_NET_TCP_FAST_RETRANSMIT |
| else |
| #endif |
| #endif |
| { |
| #ifdef CONFIG_NET_TCP_FAST_RETRANSMIT |
| /* Do fast retransmit */ |
| |
| rexmitno = ackno; |
| #ifndef CONFIG_NET_TCP_CC_NEWRENO |
| /* Reset counter */ |
| |
| TCP_WBNACK(wrb) = 0; |
| #endif |
| #endif |
| } |
| } |
| } |
| } |
| |
| /* A special case is the head of the write_q which may be partially |
| * sent and so can still have un-ACKed bytes that could get ACKed |
| * before the entire write buffer has even been sent. |
| */ |
| |
| wrb = (FAR struct tcp_wrbuffer_s *)sq_peek(&conn->write_q); |
| if (wrb && TCP_WBSENT(wrb) > 0 && TCP_SEQ_GT(ackno, TCP_WBSEQNO(wrb))) |
| { |
| uint32_t nacked; |
| |
| /* Number of bytes that were ACKed */ |
| |
| nacked = TCP_SEQ_SUB(ackno, TCP_WBSEQNO(wrb)); |
| if (nacked > TCP_WBSENT(wrb)) |
| { |
| /* More data has been ACKed then we have sent? ASSERT? */ |
| |
| nacked = TCP_WBSENT(wrb); |
| } |
| |
| ninfo("ACK: wrb=%p seqno=%" PRIu32 |
| " nacked=%" PRIu32 " sent=%u ackno=%" PRIu32 "\n", |
| wrb, TCP_WBSEQNO(wrb), nacked, TCP_WBSENT(wrb), ackno); |
| |
| /* Trim the ACKed bytes from the beginning of the write buffer. */ |
| |
| TCP_WBTRIM(wrb, nacked); |
| TCP_WBSEQNO(wrb) += nacked; |
| TCP_WBSENT(wrb) -= nacked; |
| |
| ninfo("ACK: wrb=%p seqno=%" PRIu32 " pktlen=%u sent=%u\n", |
| wrb, TCP_WBSEQNO(wrb), TCP_WBPKTLEN(wrb), TCP_WBSENT(wrb)); |
| } |
| } |
| |
| /* Check for a loss of connection */ |
| |
| if ((flags & TCP_DISCONN_EVENTS) != 0) |
| { |
| ninfo("Lost connection: %04x\n", flags); |
| |
| /* We could get here recursively through the callback actions of |
| * tcp_lost_connection(). So don't repeat that action if we have |
| * already been disconnected. |
| */ |
| |
| if (_SS_ISCONNECTED(conn->sconn.s_flags)) |
| { |
| /* Report not connected */ |
| |
| tcp_lost_connection(conn, conn->sndcb, flags); |
| } |
| |
| /* Free write buffers and terminate polling */ |
| |
| psock_lost_connection(conn, !!(flags & NETDEV_DOWN)); |
| return flags; |
| } |
| |
| #ifdef CONFIG_NET_TCP_FAST_RETRANSMIT |
| if (rexmitno != 0) |
| { |
| FAR struct tcp_wrbuffer_s *wrb; |
| FAR sq_entry_t *entry; |
| FAR sq_entry_t *next; |
| size_t sndlen; |
| int ret; |
| |
| /* According to RFC 6298 (5.4), retransmit the earliest segment |
| * that has not been acknowledged by the TCP receiver. |
| */ |
| |
| for (entry = sq_peek(&conn->unacked_q); entry; entry = next) |
| { |
| wrb = (FAR struct tcp_wrbuffer_s *)entry; |
| next = sq_next(entry); |
| |
| if (rexmitno != TCP_WBSEQNO(wrb)) |
| { |
| continue; |
| } |
| |
| /* Reconstruct the length of the earliest segment to be |
| * retransmitted. |
| */ |
| |
| sndlen = TCP_WBPKTLEN(wrb); |
| |
| if (sndlen > conn->mss) |
| { |
| sndlen = conn->mss; |
| } |
| |
| /* As we are retransmitting, the sequence number is expected |
| * already set for this write buffer. |
| */ |
| |
| DEBUGASSERT(TCP_WBSEQNO(wrb) != (unsigned)-1); |
| |
| #ifdef NEED_IPDOMAIN_SUPPORT |
| /* If both IPv4 and IPv6 support are enabled, then we will need to |
| * select which one to use when generating the outgoing packet. |
| * If only one domain is selected, then the setup is already in |
| * place and we need do nothing. |
| */ |
| |
| tcp_ip_select(conn); |
| #endif |
| /* Then set-up to send that amount of data. (this won't actually |
| * happen until the polling cycle completes). |
| */ |
| |
| tcp_setsequence(conn->sndseq, TCP_WBSEQNO(wrb)); |
| |
| ret = devif_iob_send(dev, TCP_WBIOB(wrb), sndlen, |
| 0, tcpip_hdrsize(conn)); |
| if (ret <= 0) |
| { |
| return flags; |
| } |
| |
| #ifdef CONFIG_NET_TCP_CC_NEWRENO |
| /* After Fast retransmitted, set ssthresh to the maximum of |
| * the unacked and the 2*SMSS, and enter to Fast Recovery. |
| * ssthresh = max (FlightSize / 2, 2*SMSS) referring to rfc5681 |
| * cwnd=ssthresh + 3*SMSS referring to rfc5681 |
| */ |
| |
| if (conn->flags & TCP_INFT) |
| { |
| tcp_cc_update(conn, NULL); |
| } |
| #endif |
| |
| /* Reset the retransmission timer. */ |
| |
| tcp_update_retrantimer(conn, conn->rto); |
| |
| /* Continue waiting */ |
| |
| return flags; |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_NET_TCP_SELECTIVE_ACK |
| |
| /* Check if we are being asked to retransmit s-ack data */ |
| |
| if (nsacks > 0) |
| { |
| FAR struct tcp_wrbuffer_s *wrb; |
| FAR sq_entry_t *entry; |
| FAR sq_entry_t *next; |
| uint32_t right; |
| int i; |
| |
| /* Dump s-ack edge */ |
| |
| for (i = 0, right = 0; i < nsacks; i++) |
| { |
| ninfo("TCP SACK [%d]" |
| "[%" PRIu32 " : %" PRIu32 " : %" PRIu32 "]\n", |
| i, ofosegs[i].left, ofosegs[i].right, |
| TCP_SEQ_SUB(ofosegs[i].right, ofosegs[i].left)); |
| } |
| |
| for (entry = sq_peek(&conn->unacked_q); entry; entry = next) |
| { |
| wrb = (FAR struct tcp_wrbuffer_s *)entry; |
| next = sq_next(entry); |
| |
| for (i = 0, right = 0; i < nsacks; i++) |
| { |
| /* Wrb seqno out of s-ack edge ? do retransmit ! */ |
| |
| if (TCP_SEQ_LT(TCP_WBSEQNO(wrb), ofosegs[i].left) && |
| TCP_SEQ_GTE(TCP_WBSEQNO(wrb), right)) |
| { |
| ninfo("TCP REXMIT " |
| "[%" PRIu32 " : %" PRIu32 " : %d]\n", |
| TCP_WBSEQNO(wrb), |
| TCP_SEQ_ADD(TCP_WBSEQNO(wrb), TCP_WBPKTLEN(wrb)), |
| TCP_WBPKTLEN(wrb)); |
| sq_rem(entry, &conn->unacked_q); |
| retransmit_segment(conn, (FAR void *)entry); |
| break; |
| } |
| |
| right = ofosegs[i].right; |
| } |
| } |
| |
| #ifdef CONFIG_NET_TCP_CC_NEWRENO |
| /* After Fast retransmitted, set ssthresh to the maximum of |
| * the unacked and the 2*SMSS, and enter to Fast Recovery. |
| * ssthresh = max (FlightSize / 2, 2*SMSS) referring to rfc5681 |
| * cwnd=ssthresh + 3*SMSS referring to rfc5681 |
| */ |
| |
| if (conn->flags & TCP_INFT) |
| { |
| tcp_cc_update(conn, NULL); |
| } |
| #endif |
| } |
| else |
| #endif |
| |
| /* Check if we are being asked to retransmit data */ |
| |
| if ((flags & TCP_REXMIT) != 0) |
| { |
| FAR struct tcp_wrbuffer_s *wrb; |
| FAR sq_entry_t *entry; |
| |
| ninfo("REXMIT: %04x\n", flags); |
| |
| /* If there is a partially sent write buffer at the head of the |
| * write_q? Has anything been sent from that write buffer? |
| */ |
| |
| wrb = (FAR struct tcp_wrbuffer_s *)sq_peek(&conn->write_q); |
| ninfo("REXMIT: wrb=%p sent=%u\n", wrb, wrb ? TCP_WBSENT(wrb) : 0); |
| |
| if (wrb != NULL && TCP_WBSENT(wrb) > 0) |
| { |
| FAR struct tcp_wrbuffer_s *tmp; |
| uint16_t sent; |
| |
| /* Yes.. Reset the number of bytes sent sent from |
| * the write buffer |
| */ |
| |
| sent = TCP_WBSENT(wrb); |
| if (conn->tx_unacked > sent) |
| { |
| conn->tx_unacked -= sent; |
| } |
| else |
| { |
| conn->tx_unacked = 0; |
| } |
| |
| if (conn->sent > sent) |
| { |
| conn->sent -= sent; |
| } |
| else |
| { |
| conn->sent = 0; |
| } |
| |
| TCP_WBSENT(wrb) = 0; |
| ninfo("REXMIT: wrb=%p sent=%u, " |
| "conn tx_unacked=%" PRId32 " sent=%" PRId32 "\n", |
| wrb, TCP_WBSENT(wrb), conn->tx_unacked, conn->sent); |
| |
| /* Increment the retransmit count on this write buffer. */ |
| |
| if (++TCP_WBNRTX(wrb) >= TCP_MAXRTX) |
| { |
| nwarn("WARNING: Expiring wrb=%p nrtx=%u\n", |
| wrb, TCP_WBNRTX(wrb)); |
| |
| /* The maximum retry count as been exhausted. Remove the write |
| * buffer at the head of the queue. |
| */ |
| |
| tmp = (FAR struct tcp_wrbuffer_s *)sq_remfirst(&conn->write_q); |
| DEBUGASSERT(tmp == wrb); |
| UNUSED(tmp); |
| |
| /* And return the write buffer to the free list */ |
| |
| tcp_wrbuffer_release(wrb); |
| |
| /* Notify any waiters if the write buffers have been |
| * drained. |
| */ |
| |
| psock_writebuffer_notify(conn); |
| |
| /* NOTE expired is different from un-ACKed, it is designed to |
| * represent the number of segments that have been sent, |
| * retransmitted, and un-ACKed, if expired is not zero, the |
| * connection will be closed. |
| * |
| * field expired can only be updated at TCP_ESTABLISHED state |
| */ |
| |
| conn->expired++; |
| } |
| } |
| |
| /* Move all segments that have been sent but not ACKed to the write |
| * queue again note, the un-ACKed segments are put at the head of the |
| * write_q so they can be resent as soon as possible. |
| */ |
| |
| while ((entry = sq_remlast(&conn->unacked_q)) != NULL) |
| { |
| retransmit_segment(conn, (FAR void *)entry); |
| } |
| } |
| |
| #if CONFIG_NET_SEND_BUFSIZE > 0 |
| /* Notify the send buffer available if wrbbuffer drained */ |
| |
| tcp_sendbuffer_notify(conn); |
| #endif /* CONFIG_NET_SEND_BUFSIZE */ |
| |
| /* Check if the outgoing packet is available (it may have been claimed |
| * by a sendto event serving a different thread). |
| */ |
| |
| if (dev->d_sndlen > 0) |
| { |
| /* Another thread has beat us sending data, wait for the next poll */ |
| |
| return flags; |
| } |
| |
| /* We get here if (1) not all of the data has been ACKed, (2) we have been |
| * asked to retransmit data, (3) the connection is still healthy, and (4) |
| * the outgoing packet is available for our use. In this case, we are |
| * now free to send more data to receiver -- UNLESS the buffer contains |
| * unprocessed incoming data or window size is zero. In that event, we |
| * will have to wait for the next polling cycle. |
| */ |
| |
| if ((conn->tcpstateflags & TCP_ESTABLISHED) && |
| ((flags & TCP_NEWDATA) == 0) && |
| (flags & (TCP_POLL | TCP_REXMIT | TCP_ACKDATA)) && |
| !(sq_empty(&conn->write_q)) && |
| conn->snd_wnd > 0) |
| { |
| FAR struct tcp_wrbuffer_s *wrb; |
| uint32_t predicted_seqno; |
| uint32_t seq; |
| uint32_t snd_wnd_edge; |
| size_t sndlen; |
| |
| /* Peek at the head of the write queue (but don't remove anything |
| * from the write queue yet). We know from the above test that |
| * the write_q is not empty. |
| */ |
| |
| wrb = (FAR struct tcp_wrbuffer_s *)sq_peek(&conn->write_q); |
| DEBUGASSERT(wrb); |
| |
| /* Set the sequence number for this segment. If we are |
| * retransmitting, then the sequence number will already |
| * be set for this write buffer. |
| */ |
| |
| if (TCP_WBSEQNO(wrb) == (unsigned)-1) |
| { |
| TCP_WBSEQNO(wrb) = conn->isn + conn->sent; |
| } |
| |
| /* Get the amount of data that we can send in the next packet. |
| * We will send either the remaining data in the buffer I/O |
| * buffer chain, or as much as will fit given the MSS and current |
| * window size. |
| */ |
| |
| seq = TCP_WBSEQNO(wrb) + TCP_WBSENT(wrb); |
| |
| #ifdef CONFIG_NET_TCP_CC_NEWRENO |
| snd_wnd_edge = conn->snd_wl2 + MIN(conn->snd_wnd, conn->cwnd); |
| #else |
| snd_wnd_edge = conn->snd_wl2 + conn->snd_wnd; |
| #endif |
| if (TCP_SEQ_LT(seq, snd_wnd_edge)) |
| { |
| uint32_t remaining_snd_wnd; |
| int ret; |
| |
| sndlen = TCP_WBPKTLEN(wrb) - TCP_WBSENT(wrb); |
| if (sndlen > conn->mss) |
| { |
| sndlen = conn->mss; |
| } |
| |
| remaining_snd_wnd = TCP_SEQ_SUB(snd_wnd_edge, seq); |
| if (sndlen > remaining_snd_wnd) |
| { |
| sndlen = remaining_snd_wnd; |
| } |
| |
| ninfo("SEND: wrb=%p seq=%" PRIu32 " pktlen=%u sent=%u sndlen=%zu " |
| "mss=%u snd_wnd=%u seq=%" PRIu32 |
| " remaining_snd_wnd=%" PRIu32 "\n", |
| wrb, TCP_WBSEQNO(wrb), TCP_WBPKTLEN(wrb), TCP_WBSENT(wrb), |
| sndlen, conn->mss, |
| conn->snd_wnd, seq, remaining_snd_wnd); |
| |
| /* The TCP stack updates sndseq on receipt of ACK *before* |
| * this function is called. In that case sndseq will point |
| * to the next unacknowledged byte (which might have already |
| * been sent). We will overwrite the value of sndseq here |
| * before the packet is sent. |
| */ |
| |
| tcp_setsequence(conn->sndseq, TCP_WBSEQNO(wrb) + TCP_WBSENT(wrb)); |
| |
| #ifdef NEED_IPDOMAIN_SUPPORT |
| /* If both IPv4 and IPv6 support are enabled, then we will need to |
| * select which one to use when generating the outgoing packet. |
| * If only one domain is selected, then the setup is already in |
| * place and we need do nothing. |
| */ |
| |
| tcp_ip_select(conn); |
| #endif |
| /* Then set-up to send that amount of data with the offset |
| * corresponding to the amount of data already sent. (this |
| * won't actually happen until the polling cycle completes). |
| */ |
| |
| ret = devif_iob_send(dev, TCP_WBIOB(wrb), sndlen, |
| TCP_WBSENT(wrb), tcpip_hdrsize(conn)); |
| if (ret <= 0) |
| { |
| return flags; |
| } |
| |
| /* Remember how much data we send out now so that we know |
| * when everything has been acknowledged. Just increment |
| * the amount of data sent. This will be needed in sequence |
| * number calculations. |
| */ |
| |
| conn->tx_unacked += sndlen; |
| conn->sent += sndlen; |
| |
| /* Below prediction will become true, |
| * unless retransmission occurrence |
| */ |
| |
| predicted_seqno = tcp_getsequence(conn->sndseq) + sndlen; |
| |
| if (TCP_SEQ_GT(predicted_seqno, conn->sndseq_max)) |
| { |
| conn->sndseq_max = predicted_seqno; |
| } |
| |
| ninfo("SEND: wrb=%p nrtx=%u tx_unacked=%" PRIu32 |
| " sent=%" PRIu32 "\n", |
| wrb, TCP_WBNRTX(wrb), conn->tx_unacked, conn->sent); |
| |
| /* Increment the count of bytes sent from this write buffer */ |
| |
| TCP_WBSENT(wrb) += sndlen; |
| |
| ninfo("SEND: wrb=%p sent=%u pktlen=%u\n", |
| wrb, TCP_WBSENT(wrb), TCP_WBPKTLEN(wrb)); |
| |
| /* Remove the write buffer from the write queue if the |
| * last of the data has been sent from the buffer. |
| */ |
| |
| DEBUGASSERT(TCP_WBSENT(wrb) <= TCP_WBPKTLEN(wrb)); |
| if (TCP_WBSENT(wrb) >= TCP_WBPKTLEN(wrb)) |
| { |
| FAR struct tcp_wrbuffer_s *tmp; |
| |
| ninfo("SEND: wrb=%p Move to unacked_q\n", wrb); |
| |
| tmp = (FAR struct tcp_wrbuffer_s *)sq_remfirst(&conn->write_q); |
| DEBUGASSERT(tmp == wrb); |
| UNUSED(tmp); |
| |
| /* Put the I/O buffer chain in the un-acked queue; the |
| * segment is waiting for ACK again |
| */ |
| |
| psock_insert_segment(wrb, &conn->unacked_q); |
| } |
| |
| /* Only one data can be sent by low level driver at once, |
| * tell the caller stop polling the other connection. |
| */ |
| |
| flags &= ~TCP_POLL; |
| } |
| } |
| else |
| { |
| tcp_set_zero_probe(conn, flags); |
| } |
| |
| /* Continue waiting */ |
| |
| return flags; |
| } |
| |
| /**************************************************************************** |
| * Name: tcp_max_wrb_size |
| * |
| * Description: |
| * Calculate the desired amount of data for a single |
| * struct tcp_wrbuffer_s. |
| * |
| ****************************************************************************/ |
| |
| static uint32_t tcp_max_wrb_size(FAR struct tcp_conn_s *conn) |
| { |
| const uint32_t mss = conn->mss; |
| uint32_t size; |
| |
| /* a few segments should be fine */ |
| |
| size = 4 * mss; |
| |
| /* but it should not hog too many IOB buffers */ |
| |
| if (size > CONFIG_IOB_NBUFFERS * CONFIG_IOB_BUFSIZE / 2) |
| { |
| size = CONFIG_IOB_NBUFFERS * CONFIG_IOB_BUFSIZE / 2; |
| } |
| |
| /* also, we prefer a multiple of mss */ |
| |
| if (size > mss) |
| { |
| const uint32_t odd = size % mss; |
| size -= odd; |
| } |
| |
| DEBUGASSERT(size > 0); |
| ninfo("tcp_max_wrb_size = %" PRIu32 " for conn %p\n", size, conn); |
| return size; |
| } |
| |
| /**************************************************************************** |
| * Name: tcp_send_gettimeout |
| * |
| * Description: |
| * Calculate the send timeout |
| * |
| ****************************************************************************/ |
| |
| static unsigned int tcp_send_gettimeout(clock_t start, unsigned int timeout) |
| { |
| unsigned int elapse; |
| |
| if (timeout != UINT_MAX) |
| { |
| elapse = TICK2MSEC(clock_systime_ticks() - start); |
| if (elapse >= timeout) |
| { |
| timeout = 0; |
| } |
| else |
| { |
| timeout -= elapse; |
| } |
| } |
| |
| return timeout; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: psock_tcp_send |
| * |
| * Description: |
| * psock_tcp_send() call may be used only when the TCP socket is in a |
| * connected state (so that the intended recipient is known). |
| * |
| * Input Parameters: |
| * psock An instance of the internal socket structure. |
| * buf Data to send |
| * len Length of data to send |
| * flags Send flags |
| * |
| * Returned Value: |
| * On success, returns the number of characters sent. On error, |
| * -1 is returned, and errno is set appropriately: |
| * |
| * EAGAIN or EWOULDBLOCK |
| * The socket is marked non-blocking and the requested operation |
| * would block. |
| * EBADF |
| * An invalid descriptor was specified. |
| * ECONNRESET |
| * Connection reset by peer. |
| * EDESTADDRREQ |
| * The socket is not connection-mode, and no peer address is set. |
| * EFAULT |
| * An invalid user space address was specified for a parameter. |
| * EINTR |
| * A signal occurred before any data was transmitted. |
| * EINVAL |
| * Invalid argument passed. |
| * EISCONN |
| * The connection-mode socket was connected already but a recipient |
| * was specified. (Now either this error is returned, or the recipient |
| * specification is ignored.) |
| * EMSGSIZE |
| * The socket type requires that message be sent atomically, and the |
| * size of the message to be sent made this impossible. |
| * ENOBUFS |
| * The output queue for a network interface was full. This generally |
| * indicates that the interface has stopped sending, but may be |
| * caused by transient congestion. |
| * ENOMEM |
| * No memory available. |
| * ENOTCONN |
| * The socket is not connected, and no target has been given. |
| * ENOTSOCK |
| * The argument s is not a socket. |
| * EPIPE |
| * The local end has been shut down on a connection oriented socket. |
| * In this case the process will also receive a SIGPIPE unless |
| * MSG_NOSIGNAL is set. |
| * |
| ****************************************************************************/ |
| |
| ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf, |
| size_t len, int flags) |
| { |
| FAR struct tcp_conn_s *conn; |
| FAR struct tcp_wrbuffer_s *wrb; |
| FAR const uint8_t *cp; |
| unsigned int timeout; |
| ssize_t result = 0; |
| bool nonblock; |
| int ret = OK; |
| clock_t start; |
| |
| if (psock == NULL || psock->s_type != SOCK_STREAM || |
| psock->s_conn == NULL) |
| { |
| nerr("ERROR: Invalid socket\n"); |
| ret = -EBADF; |
| goto errout; |
| } |
| |
| conn = psock->s_conn; |
| |
| if (!_SS_ISCONNECTED(conn->sconn.s_flags)) |
| { |
| nerr("ERROR: Not connected\n"); |
| ret = -ENOTCONN; |
| goto errout; |
| } |
| |
| /* Make sure that we have the IP address mapping */ |
| |
| #if defined(CONFIG_NET_ARP_SEND) || defined(CONFIG_NET_ICMPv6_NEIGHBOR) |
| #ifdef CONFIG_NET_ARP_SEND |
| #ifdef CONFIG_NET_ICMPv6_NEIGHBOR |
| if (psock->s_domain == PF_INET) |
| #endif |
| { |
| /* Make sure that the IP address mapping is in the ARP table */ |
| |
| ret = arp_send(conn->u.ipv4.raddr); |
| } |
| #endif /* CONFIG_NET_ARP_SEND */ |
| |
| #ifdef CONFIG_NET_ICMPv6_NEIGHBOR |
| #ifdef CONFIG_NET_ARP_SEND |
| else |
| #endif |
| { |
| /* Make sure that the IP address mapping is in the Neighbor Table */ |
| |
| ret = icmpv6_neighbor(NULL, conn->u.ipv6.raddr); |
| } |
| #endif /* CONFIG_NET_ICMPv6_NEIGHBOR */ |
| |
| /* Did we successfully get the address mapping? */ |
| |
| if (ret < 0) |
| { |
| nerr("ERROR: Not reachable\n"); |
| ret = -ENETUNREACH; |
| goto errout; |
| } |
| #endif /* CONFIG_NET_ARP_SEND || CONFIG_NET_ICMPv6_NEIGHBOR */ |
| |
| nonblock = _SS_ISNONBLOCK(conn->sconn.s_flags) || |
| (flags & MSG_DONTWAIT) != 0; |
| start = clock_systime_ticks(); |
| timeout = _SO_TIMEOUT(conn->sconn.s_sndtimeo); |
| |
| /* Dump the incoming buffer */ |
| |
| BUF_DUMP("psock_tcp_send", buf, len); |
| |
| cp = buf; |
| while (len > 0) |
| { |
| uint32_t max_wrb_size; |
| unsigned int off; |
| size_t chunk_len = len; |
| ssize_t chunk_result; |
| |
| net_lock(); |
| |
| /* Now that we have the network locked, we need to check the connection |
| * state again to ensure the connection is still valid. |
| */ |
| |
| if (!_SS_ISCONNECTED(conn->sconn.s_flags)) |
| { |
| nerr("ERROR: No longer connected\n"); |
| ret = -ENOTCONN; |
| goto errout_with_lock; |
| } |
| |
| /* Allocate resources to receive a callback */ |
| |
| if (conn->sndcb == NULL) |
| { |
| conn->sndcb = tcp_callback_alloc(conn); |
| |
| /* Test if the callback has been allocated */ |
| |
| if (conn->sndcb == NULL) |
| { |
| /* A buffer allocation error occurred */ |
| |
| nerr("ERROR: Failed to allocate callback\n"); |
| ret = nonblock ? -EAGAIN : -ENOMEM; |
| goto errout_with_lock; |
| } |
| } |
| |
| /* Set up the callback in the connection */ |
| |
| conn->sndcb->flags = (TCP_ACKDATA | TCP_REXMIT | TCP_POLL | |
| TCP_DISCONN_EVENTS); |
| conn->sndcb->priv = (FAR void *)conn; |
| conn->sndcb->event = psock_send_eventhandler; |
| |
| #if CONFIG_NET_SEND_BUFSIZE > 0 |
| /* If the send buffer size exceeds the send limit, |
| * wait for the write buffer to be released |
| */ |
| |
| while (tcp_wrbuffer_inqueue_size(conn) >= conn->snd_bufs) |
| { |
| if (nonblock) |
| { |
| ret = -EAGAIN; |
| goto errout_with_lock; |
| } |
| |
| ret = net_sem_timedwait_uninterruptible(&conn->snd_sem, |
| tcp_send_gettimeout(start, timeout)); |
| if (ret < 0) |
| { |
| if (ret == -ETIMEDOUT) |
| { |
| ret = -EAGAIN; |
| } |
| |
| goto errout_with_lock; |
| } |
| } |
| #endif /* CONFIG_NET_SEND_BUFSIZE */ |
| |
| while (true) |
| { |
| struct iob_s *iob; |
| |
| /* Allocate a write buffer. Careful, the network will be |
| * momentarily unlocked here. |
| */ |
| |
| /* Try to coalesce into the last wrb. |
| * |
| * But only when it might yield larger segments. |
| * (REVISIT: It might make sense to lift this condition. |
| * IOB boundaries and segment boundaries usually do not match. |
| * It makes sense to save the number of IOBs.) |
| * |
| * Also, for simplicity, do it only when we haven't sent anything |
| * from the the wrb yet. |
| */ |
| |
| max_wrb_size = tcp_max_wrb_size(conn); |
| wrb = (FAR struct tcp_wrbuffer_s *)sq_tail(&conn->write_q); |
| if (wrb != NULL && TCP_WBSENT(wrb) == 0 && TCP_WBNRTX(wrb) == 0 && |
| TCP_WBPKTLEN(wrb) < max_wrb_size && |
| (TCP_WBPKTLEN(wrb) % conn->mss) != 0) |
| { |
| wrb = (FAR struct tcp_wrbuffer_s *)sq_remlast(&conn->write_q); |
| ninfo("coalesce %zu bytes to wrb %p (%" PRIu16 ")\n", len, wrb, |
| TCP_WBPKTLEN(wrb)); |
| DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0); |
| } |
| else if (nonblock) |
| { |
| wrb = tcp_wrbuffer_tryalloc(); |
| ninfo("new wrb %p (non blocking)\n", wrb); |
| } |
| else |
| { |
| wrb = tcp_wrbuffer_timedalloc(tcp_send_gettimeout(start, |
| timeout)); |
| ninfo("new wrb %p\n", wrb); |
| } |
| |
| if (wrb == NULL) |
| { |
| /* A buffer allocation error occurred */ |
| |
| nerr("ERROR: Failed to allocate write buffer\n"); |
| |
| if (nonblock || timeout != UINT_MAX) |
| { |
| ret = -EAGAIN; |
| } |
| else |
| { |
| ret = -ENOMEM; |
| } |
| |
| goto errout_with_lock; |
| } |
| |
| /* Initialize the write buffer */ |
| |
| TCP_WBSEQNO(wrb) = (unsigned)-1; |
| TCP_WBNRTX(wrb) = 0; |
| |
| off = TCP_WBPKTLEN(wrb); |
| if (off + chunk_len > max_wrb_size) |
| { |
| chunk_len = max_wrb_size - off; |
| } |
| |
| /* Copy the user data into the write buffer. We cannot wait for |
| * buffer space. |
| */ |
| |
| /* The return value from TCP_WBTRYCOPYIN is either OK or |
| * -ENOMEM if less than the entire data chunk could be allocated. |
| * If -ENOMEM is returned, check if at least a part of the data |
| * chunk was allocated. If more than zero bytes were sent |
| * we return that number and let the caller deal with sending the |
| * remaining data. |
| */ |
| |
| chunk_result = TCP_WBTRYCOPYIN(wrb, cp, chunk_len, off); |
| if (chunk_result == -ENOMEM) |
| { |
| if (TCP_WBPKTLEN(wrb) > 0) |
| { |
| DEBUGASSERT(TCP_WBPKTLEN(wrb) >= off); |
| chunk_result = TCP_WBPKTLEN(wrb) - off; |
| ninfo("INFO: Allocated part of the requested data " |
| "%zd/%zu\n", |
| chunk_result, chunk_len); |
| |
| /* Note: chunk_result here can be 0 if we are trying |
| * to coalesce into the existing buffer and we failed |
| * to add anything. |
| */ |
| |
| DEBUGASSERT(chunk_result >= 0); |
| } |
| else |
| { |
| chunk_result = 0; |
| } |
| } |
| else |
| { |
| DEBUGASSERT(chunk_result == chunk_len); |
| } |
| |
| if (chunk_result > 0) |
| { |
| break; |
| } |
| |
| /* release wrb */ |
| |
| if (TCP_WBPKTLEN(wrb) > 0) |
| { |
| DEBUGASSERT(TCP_WBSENT(wrb) == 0); |
| DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0); |
| sq_addlast(&wrb->wb_node, &conn->write_q); |
| } |
| else |
| { |
| tcp_wrbuffer_release(wrb); |
| } |
| |
| if (nonblock || (timeout != UINT_MAX && |
| tcp_send_gettimeout(start, timeout) == 0)) |
| { |
| nerr("ERROR: Failed to add data to the I/O chain\n"); |
| ret = -EAGAIN; |
| goto errout_with_lock; |
| } |
| |
| /* Wait for at least one IOB getting available. |
| * |
| * Note: net_ioballoc releases the network lock when blocking. |
| * It allows our write_q being drained in the meantime. Otherwise, |
| * we risk a deadlock with other threads competing on IOBs. |
| */ |
| |
| iob = net_iobtimedalloc(true, tcp_send_gettimeout(start, timeout)); |
| if (iob != NULL) |
| { |
| iob_free_chain(iob); |
| } |
| } |
| |
| /* Dump I/O buffer chain */ |
| |
| TCP_WBDUMP("I/O buffer chain", wrb, TCP_WBPKTLEN(wrb), 0); |
| |
| /* psock_send_eventhandler() will send data in FIFO order from the |
| * conn->write_q |
| */ |
| |
| sq_addlast(&wrb->wb_node, &conn->write_q); |
| ninfo("Queued WRB=%p pktlen=%u write_q(%p,%p)\n", |
| wrb, TCP_WBPKTLEN(wrb), |
| conn->write_q.head, conn->write_q.tail); |
| |
| /* Notify the device driver of the availability of TX data */ |
| |
| tcp_send_txnotify(psock, conn); |
| net_unlock(); |
| |
| if (chunk_result == 0) |
| { |
| DEBUGASSERT(nonblock); |
| if (result == 0) |
| { |
| result = -EAGAIN; |
| } |
| |
| break; |
| } |
| |
| if (chunk_result < 0) |
| { |
| if (result == 0) |
| { |
| result = chunk_result; |
| } |
| |
| break; |
| } |
| |
| DEBUGASSERT(chunk_result <= len); |
| DEBUGASSERT(chunk_result <= chunk_len); |
| DEBUGASSERT(result >= 0); |
| cp += chunk_result; |
| len -= chunk_result; |
| result += chunk_result; |
| } |
| |
| /* Check for errors. Errors are signaled by negative errno values |
| * for the send length |
| */ |
| |
| if (result < 0) |
| { |
| ret = result; |
| goto errout; |
| } |
| |
| /* Return the number of bytes actually sent */ |
| |
| return result; |
| |
| errout_with_lock: |
| net_unlock(); |
| |
| errout: |
| if (result > 0) |
| { |
| return result; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: psock_tcp_cansend |
| * |
| * Description: |
| * psock_tcp_cansend() returns a value indicating if a write to the socket |
| * would block. No space in the buffer is actually reserved, so it is |
| * possible that the write may still block if the buffer is filled by |
| * another means. |
| * |
| * Input Parameters: |
| * conn The TCP connection of interest |
| * |
| * Returned Value: |
| * OK |
| * At least one byte of data could be successfully written. |
| * -EWOULDBLOCK |
| * There is no room in the output buffer. |
| * -EBADF |
| * An invalid descriptor was specified. |
| * -ENOTCONN |
| * The socket is not connected. |
| * |
| ****************************************************************************/ |
| |
| int psock_tcp_cansend(FAR struct tcp_conn_s *conn) |
| { |
| /* Verify that we received a valid socket */ |
| |
| if (!conn) |
| { |
| nerr("ERROR: Invalid socket\n"); |
| return -EBADF; |
| } |
| |
| /* Verify that this is connected TCP socket */ |
| |
| if (!_SS_ISCONNECTED(conn->sconn.s_flags)) |
| { |
| nwarn("WARNING: Not connected\n"); |
| return -ENOTCONN; |
| } |
| |
| /* In order to setup the send, we need to have at least one free write |
| * buffer head and at least one free IOB to initialize the write buffer |
| * head. |
| * |
| * REVISIT: The send will still block if we are unable to buffer |
| * the entire user-provided buffer which may be quite large. |
| * We will almost certainly need to have more than one free IOB, |
| * but we don't know how many more. |
| */ |
| |
| if (tcp_wrbuffer_test() < 0 || iob_navail(true) <= 0 |
| #if CONFIG_NET_SEND_BUFSIZE > 0 |
| || tcp_wrbuffer_inqueue_size(conn) >= conn->snd_bufs |
| #endif |
| ) |
| { |
| return -EWOULDBLOCK; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: tcp_sendbuffer_notify |
| * |
| * Description: |
| * Notify the send buffer semaphore |
| * |
| * Input Parameters: |
| * conn - The TCP connection of interest |
| * |
| * Assumptions: |
| * Called from user logic with the network locked. |
| * |
| ****************************************************************************/ |
| |
| #if CONFIG_NET_SEND_BUFSIZE > 0 |
| void tcp_sendbuffer_notify(FAR struct tcp_conn_s *conn) |
| { |
| int val = 0; |
| |
| nxsem_get_value(&conn->snd_sem, &val); |
| if (val < 0) |
| { |
| nxsem_post(&conn->snd_sem); |
| } |
| } |
| #endif /* CONFIG_NET_SEND_BUFSIZE */ |
| |
| #endif /* CONFIG_NET && CONFIG_NET_TCP && CONFIG_NET_TCP_WRITE_BUFFERS */ |