/*
* VMware vSockets Driver
*
* Copyright (C) 2009-2013 VMware, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation version 2 and no later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/stddef.h>
#include <net/sock.h>
#include "vmci_transport_notify.h"
#define PKT_FIELD(vsk, field_name) (vmci_trans(vsk)->notify.pkt.field_name)
static bool vmci_transport_notify_waiting_write(struct vsock_sock *vsk)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
bool retval;
u64 notify_limit;
if (!PKT_FIELD(vsk, peer_waiting_write))
return false;
#ifdef VSOCK_OPTIMIZATION_FLOW_CONTROL
/* When the sender blocks, we take that as a sign that the sender is
* faster than the receiver. To reduce the transmit rate of the sender,
* we delay the sending of the read notification by decreasing the
* write_notify_window. The notification is delayed until the number of
* bytes used in the queue drops below the write_notify_window.
*/
if (!PKT_FIELD(vsk, peer_waiting_write_detected)) {
PKT_FIELD(vsk, peer_waiting_write_detected) = true;
if (PKT_FIELD(vsk, write_notify_window) < PAGE_SIZE) {
PKT_FIELD(vsk, write_notify_window) =
PKT_FIELD(vsk, write_notify_min_window);
} else {
PKT_FIELD(vsk, write_notify_window) -= PAGE_SIZE;
if (PKT_FIELD(vsk, write_notify_window) <
PKT_FIELD(vsk, write_notify_min_window))
PKT_FIELD(vsk, write_notify_window) =
PKT_FIELD(vsk, write_notify_min_window);
}
}
notify_limit = vmci_trans(vsk)->consume_size -
PKT_FIELD(vsk, write_notify_window);
#else
notify_limit = 0;
#endif
/* For now we ignore the wait information and just see if the free
* space exceeds the notify limit. Note that improving this function
* to be more intelligent will not require a protocol change and will
* retain compatibility between endpoints with mixed versions of this
* function.
*
* The notify_limit is used to delay notifications in the case where
* flow control is enabled. Below the test is expressed in terms of
* free space in the queue: if free_space > ConsumeSize -
* write_notify_window then notify An alternate way of expressing this
* is to rewrite the expression to use the data ready in the receive
* queue: if write_notify_window > bufferReady then notify as
* free_space == ConsumeSize - bufferReady.
*/
retval = vmci_qpair_consume_free_space(vmci_trans(vsk)->qpair) >
notify_limit;
#ifdef VSOCK_OPTIMIZATION_FLOW_CONTROL
if (retval) {
/*
* Once we notify the peer, we reset the detected flag so the
* next wait will again cause a decrease in the window size.
*/
PKT_FIELD(vsk, peer_waiting_write_detected) = false;
}
#endif
return retval;
#else
return true;
#endif
}
static bool vmci_transport_notify_waiting_read(struct vsock_sock *vsk)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
if (!PKT_FIELD(vsk, peer_waiting_read))
return false;
/* For now we ignore the wait information and just see if there is any
* data for our peer to read. Note that improving this function to be
* more intelligent will not require a protocol change and will retain
* compatibility between endpoints with mixed versions of this
* function.
*/
return vmci_qpair_produce_buf_ready(vmci_trans(vsk)->qpair) > 0;
#else
return true;
#endif
}
static void
vmci_transport_handle_waiting_read(struct sock *sk,
struct vmci_transport_packet *pkt,
bool bottom_half,
struct sockaddr_vm *dst,
struct sockaddr_vm *src)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
struct vsock_sock *vsk;
vsk = vsock_sk(sk);
PKT_FIELD(vsk, peer_waiting_read) = true;
memcpy(&PKT_FIELD(vsk, peer_waiting_read_info), &pkt->u.wait,
sizeof(PKT_FIELD(vsk, peer_waiting_read_info)));
if (vmci_transport_notify_waiting_read(vsk)) {
bool sent;
if (bottom_half)
sent = vmci_transport_send_wrote_bh(dst, src) > 0;
else
sent = vmci_transport_send_wrote(sk) > 0;
if (sent)
PKT_FIELD(vsk, peer_waiting_read) = false;
}
#endif
}
static void
vmci_transport_handle_waiting_write(struct sock *sk,
struct vmci_transport_packet *pkt,
bool bottom_half,
struct sockaddr_vm *dst,
struct sockaddr_vm *src)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
struct vsock_sock *vsk;
vsk = vsock_sk(sk);
PKT_FIELD(vsk, peer_waiting_write) = true;
memcpy(&PKT_FIELD(vsk, peer_waiting_write_info), &pkt->u.wait,
sizeof(PKT_FIELD(vsk, peer_waiting_write_info)));
if (vmci_transport_notify_waiting_write(vsk)) {
bool sent;
if (bottom_half)
sent = vmci_transport_send_read_bh(dst, src) > 0;
else
sent = vmci_transport_send_read(sk) > 0;
if (sent)
PKT_FIELD(vsk, peer_waiting_write) = false;
}
#endif
}
static void
vmci_transport_handle_read(struct sock *sk,
struct vmci_transport_packet *pkt,
bool bottom_half,
struct sockaddr_vm *dst, struct sockaddr_vm *src)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
struct vsock_sock *vsk;
vsk = vsock_sk(sk);
PKT_FIELD(vsk, sent_waiting_write) = false;
#endif
sk->sk_write_space(sk);
}
static bool send_waiting_read(struct sock *sk, u64 room_needed)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
struct vsock_sock *vsk;
struct vmci_transport_waiting_info waiting_info;
u64 tail;
u64 head;
u64 room_left;
bool ret;
vsk = vsock_sk(sk);
if (PKT_FIELD(vsk, sent_waiting_read))
return true;
if (PKT_FIELD(vsk, write_notify_window) <
vmci_trans(vsk)->consume_size)
PKT_FIELD(vsk, write_notify_window) =
min(PKT_FIELD(vsk, write_notify_window) + PAGE_SIZE,
vmci_trans(vsk)->consume_size);
vmci_qpair_get_consume_indexes(vmci_trans(vsk)->qpair, &tail, &head);
room_left = vmci_trans(vsk)->consume_size - head;
if (room_needed >= room_left) {
waiting_info.offset = room_needed - room_left;
waiting_info.generation =
PKT_FIELD(vsk, consume_q_generation) + 1;
} else {
waiting_info.offset = head + room_needed;
waiting_info.generation = PKT_FIELD(vsk, consume_q_generation);
}
ret = vmci_transport_send_waiting_read(sk, &waiting_info) > 0;
if (ret)
PKT_FIELD(vsk, sent_waiting_read) = true;
return ret;
#else
return true;
#endif
}
static bool send_waiting_write(struct sock *sk, u64 room_needed)
{
#if defined(VSOCK_OPTIMIZATION_WAITING_NOTIFY)
struct vsock_sock *vsk;
struct vmci_transport_waiting_info waiting_info;
u64 tail;
u64 head;
u64 room_left;
bool ret;
vsk = vsock_sk(sk);
if (PKT_FIELD(vsk, sent_waiting_write))
return true;
vmci_qpair_get_produce_indexes(vmci_trans(vsk)->qpair, &tail, &head);
room_left = vmci_trans(vsk)->produce_size - tail;
if (room_needed + 1 >= room_left) {
/* Wraps around to current generation. */
waiting_info.offset = room_needed + 1 - room_left;
waiting_info.generation = PKT_FIELD(vsk, produce_q_generation);
} else {
waiting_info.offset = tail + room_needed + 1;
waiting_info.generation =
PKT_FIELD(vsk, produce_q_generation) - 1;
}
ret = vmci_transport_send_waiting_write(sk, &waiting_info) > 0;
if (ret)
PKT_FIELD(vsk, sent_waiting_write) = true;
return ret;
#else
return true;
#endif
}
static int vmci_transport_send_read_notification(struct sock *sk)
{
struct vsock_sock *vsk;
bool sent_read;
unsigned int retries;
int err;
vsk = vsock_sk(sk);
sent_read = false;
retries = 0;
err = 0;
if (vmci_transport_notify_waiting_write(vsk)) {
/* Notify the peer that we have read, retrying the send on
* failure up to our maximum value. XXX For now we just log
* the failure, but later we should schedule a work item to
* hand