From f06b209dcc8a256d2b57bf1ebfe25611648879ab Mon Sep 17 00:00:00 2001
From: Lars Ellenberg <lars.ellenberg@linbit.com>
Date: Fri, 20 Jul 2018 12:42:05 +0200
Subject: [PATCH] drbd: introduce module parameter protocol_version_min

Some features are "disabled" unless we "know" that "all" peers
understand at least a certain DRBD wire protocol version.

And we can only know that after we have seen them at least once
during the current lifetime of this DRBD resource, we do not persist
that information anywhere.

Some may want to reduce backward compatibility (reject very old peers)
but allow features even before the initial handshake to all peers,
by adjusting the exposed "minimum supported protocol version".

Currently I'm only aware of a single affected feature:
"resize --assume-peer-has-space --assume-clean"
while some peer is currently not connected.
If it had not been connected to at all yet,
this would fail with "at least protocol 93 required".

This can now be avoided by setting "protocol_version_min=93"
(or 101, or 110 or whatever) before configuring that resource.

Also expose the agreed protocol version per connection in debugfs,
and cache the current minimum aggreed protocol version over all
connections in the resource object.

That information is then used to possibly allow larger epochs even for
"batch bios" (discards, zero-out), which in older than 9.0.15
(protocol version 114) would otherwise be too likely to congest on the
receiving peers in their activity log.
---
 drbd/drbd_debugfs.c  |  1 +
 drbd/drbd_int.h      |  3 +++
 drbd/drbd_main.c     | 30 ++++++++++++++++++++++++++++++
 drbd/drbd_receiver.c | 11 ++++++-----
 drbd/drbd_req.c      | 19 ++++++++++++++-----
 drbd/drbd_state.c    |  6 ++++++
 6 files changed, 60 insertions(+), 10 deletions(-)

diff --git a/drbd/drbd_debugfs.c b/drbd/drbd_debugfs.c
index 6000a602..ce6c3018 100644
--- a/drbd/drbd_debugfs.c
+++ b/drbd/drbd_debugfs.c
@@ -863,6 +863,7 @@ static int connection_debug_show(struct seq_file *m, void *ignored)
 	              "           active_ee_cnt: %d\n",
 		atomic_read(&connection->done_ee_cnt),
 		atomic_read(&connection->active_ee_cnt));
+	seq_printf(m, "      agreed_pro_version: %d\n", connection->agreed_pro_version);
 	return 0;
 }
 
diff --git a/drbd/drbd_int.h b/drbd/drbd_int.h
index 0683be24..53aedf89 100644
--- a/drbd/drbd_int.h
+++ b/drbd/drbd_int.h
@@ -86,6 +86,7 @@
 
 /* module parameter, defined in drbd_main.c */
 extern unsigned int drbd_minor_count;
+extern unsigned int drbd_protocol_version_min;
 
 #ifdef CONFIG_DRBD_FAULT_INJECTION
 extern int drbd_enable_faults;
@@ -890,6 +891,8 @@ struct drbd_resource {
 	atomic_t current_tle_nr;	/* transfer log epoch number */
 	unsigned current_tle_writes;	/* writes seen within this tl epoch */
 
+	unsigned cached_min_aggreed_protocol_version;
+
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) && !defined(cpumask_bits)
 	cpumask_t cpu_mask[1];
diff --git a/drbd/drbd_main.c b/drbd/drbd_main.c
index 18199daa..7b7b878d 100644
--- a/drbd/drbd_main.c
+++ b/drbd/drbd_main.c
@@ -118,6 +118,35 @@ char drbd_usermode_helper[80] = "/sbin/drbdadm";
 module_param_named(minor_count, drbd_minor_count, uint, 0444);
 module_param_string(usermode_helper, drbd_usermode_helper, sizeof(drbd_usermode_helper), 0644);
 
+static int param_set_drbd_protocol_version(const char *s, const struct kernel_param *kp)
+{
+	unsigned long long tmp;
+	unsigned int *res = kp->arg;
+	int rv;
+
+	rv = kstrtoull(s, 0, &tmp);
+	if (rv < 0)
+		return rv;
+	if (tmp < PRO_VERSION_MIN || tmp > PRO_VERSION_MAX)
+		return -ERANGE;
+	*res = tmp;
+	return 0;
+}
+
+#define param_check_drbd_protocol_version	param_check_uint
+#define param_get_drbd_protocol_version		param_get_uint
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+/* 9bbb9e5a3310 param: use ops in struct kernel_param, rather than get and set fns directly */
+const struct kernel_param_ops param_ops_drbd_protocol_version = {
+	.set = param_set_drbd_protocol_version,
+	.get = param_get_drbd_protocol_version,
+};
+#endif
+
+unsigned int drbd_protocol_version_min = PRO_VERSION_MIN;
+module_param_named(protocol_version_min, drbd_protocol_version_min, drbd_protocol_version, 0644);
+
+
 /* in 2.6.x, our device mapping and config info contains our virtual gendisks
  * as member "struct gendisk *vdisk;"
  */
@@ -3436,6 +3465,7 @@ struct drbd_resource *drbd_create_resource(const char *name,
 	drbd_thread_init(resource, &resource->worker, drbd_worker, "worker");
 	drbd_thread_start(&resource->worker);
 	drbd_debugfs_resource_add(resource);
+	resource->cached_min_aggreed_protocol_version = drbd_protocol_version_min;
 
 	list_add_tail_rcu(&resource->resources, &drbd_resources);
 
diff --git a/drbd/drbd_receiver.c b/drbd/drbd_receiver.c
index a9e22d5a..d1d1a337 100644
--- a/drbd/drbd_receiver.c
+++ b/drbd/drbd_receiver.c
@@ -699,8 +699,9 @@ start:
 		return false;
 	}
 
-	/* Assume that the peer only understands protocol 80 until we know better.  */
-	connection->agreed_pro_version = 80;
+	/* Assume that the peer only understands our minimum supported
+	 * protocol version; until we know better. */
+	connection->agreed_pro_version = drbd_protocol_version_min;
 
 	err = transport->ops->connect(transport);
 	if (err == -EAGAIN) {
@@ -7779,7 +7780,7 @@ static int drbd_send_features(struct drbd_connection *connection)
 	if (!p)
 		return -EIO;
 	memset(p, 0, sizeof(*p));
-	p->protocol_min = cpu_to_be32(PRO_VERSION_MIN);
+	p->protocol_min = cpu_to_be32(drbd_protocol_version_min);
 	p->protocol_max = cpu_to_be32(PRO_VERSION_MAX);
 	p->sender_node_id = cpu_to_be32(connection->resource->res_opts.node_id);
 	p->receiver_node_id = cpu_to_be32(connection->peer_node_id);
@@ -7836,10 +7837,10 @@ int drbd_do_features(struct drbd_connection *connection)
 		p->protocol_max = p->protocol_min;
 
 	if (PRO_VERSION_MAX < p->protocol_min ||
-	    PRO_VERSION_MIN > p->protocol_max) {
+	    drbd_protocol_version_min > p->protocol_max) {
 		drbd_err(connection, "incompatible DRBD dialects: "
 		    "I support %d-%d, peer supports %d-%d\n",
-		    PRO_VERSION_MIN, PRO_VERSION_MAX,
+		    drbd_protocol_version_min, PRO_VERSION_MAX,
 		    p->protocol_min, p->protocol_max);
 		return -1;
 	}
diff --git a/drbd/drbd_req.c b/drbd/drbd_req.c
index fe6a956a..8b640b89 100644
--- a/drbd/drbd_req.c
+++ b/drbd/drbd_req.c
@@ -953,15 +953,24 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what,
 		D_ASSERT(device, req->net_rq_state[idx] & RQ_NET_PENDING);
 		mod_rq_state(req, m, peer_device, 0, RQ_NET_QUEUED|RQ_EXP_BARR_ACK);
 
-		/* close the epoch, in case it outgrew the limit,
-		 * or this is a "batch bio" */
-		if (!(req->local_rq_state & (RQ_UNMAP|RQ_WSAME|RQ_ZEROES))) {
+		/* Close the epoch, in case it outgrew the limit.
+		 * Or if this is a "batch bio", and some of our peers is "old",
+		 * because a batch bio "storm" (like, large scale discarding
+		 * during mkfs time) would be likely to starve out the peers
+		 * activity log, if it is smaller than ours (or we don't have
+		 * any).  And a fix for the resulting potential distributed
+		 * deadlock was only implemented with P_CONFIRM_STABLE with
+		 * protocol version 114.
+		 */
+		if (device->resource->cached_min_aggreed_protocol_version < 114 &&
+		    (req->local_rq_state & (RQ_UNMAP|RQ_WSAME|RQ_ZEROES)))
+			p = 1;
+		else {
 			rcu_read_lock();
 			nc = rcu_dereference(peer_device->connection->transport.net_conf);
 			p = nc->max_epoch_size;
 			rcu_read_unlock();
-		} else
-			p = 1;
+		}
 		if (device->resource->current_tle_writes >= p)
 			start_new_tl_epoch(device->resource);
 		break;
diff --git a/drbd/drbd_state.c b/drbd/drbd_state.c
index 6d33cb9e..09a23802 100644
--- a/drbd/drbd_state.c
+++ b/drbd/drbd_state.c
@@ -689,6 +689,7 @@ static enum drbd_state_rv ___end_state_change(struct drbd_resource *resource, st
 	enum chg_state_flags flags = resource->state_change_flags;
 	struct drbd_connection *connection;
 	struct drbd_device *device;
+	unsigned int pro_ver;
 	int vnr;
 
 	if (flags & CS_ABORT)
@@ -716,14 +717,19 @@ static enum drbd_state_rv ___end_state_change(struct drbd_resource *resource, st
 	resource->susp_nod[NOW] = resource->susp_nod[NEW];
 	resource->cached_susp = resource_is_suspended(resource, NEW);
 
+	pro_ver = PRO_VERSION_MAX;
 	for_each_connection(connection, resource) {
 		connection->cstate[NOW] = connection->cstate[NEW];
 		connection->peer_role[NOW] = connection->peer_role[NEW];
 		connection->susp_fen[NOW] = connection->susp_fen[NEW];
 
+		pro_ver = min_t(unsigned int, pro_ver,
+			connection->agreed_pro_version);
+
 		wake_up(&connection->ping_wait);
 		wake_up(&connection->ee_wait);
 	}
+	resource->cached_min_aggreed_protocol_version = pro_ver;
 
 	idr_for_each_entry(&resource->devices, device, vnr) {
 		struct drbd_peer_device *peer_device;
-- 
2.34.1

