Blame SOURCES/0002-Support-for-configurable-RSS-hash-key.patch

9b550e
From 86c0326c06b2de611f438a453fae51512747e831 Mon Sep 17 00:00:00 2001
9b550e
From: Venkat Duvvuru <VenkatKumar.Duvvuru@Emulex.com>
9b550e
Date: Tue, 22 Jul 2014 17:51:07 +0530
9b550e
Subject: [PATCH 2/4] ethtool: Support for configurable RSS hash key
9b550e
9b550e
This ethtool patch will primarily implement the parser for the options provided
9b550e
by the user for get and set rxfh before invoking the ioctl.
9b550e
This patch also has
9b550e
1. Ethtool man page changes which describes the Usage of
9b550e
   get and set rxfh options.
9b550e
2. Test cases for get and set rxfh in test-cmdline.c
9b550e
9b550e
Signed-off-by: Venkat Duvvuru <VenkatKumar.Duvvuru@Emulex.com>
9b550e
---
9b550e
 ethtool.8.in   |  18 ++-
9b550e
 ethtool.c      | 393 ++++++++++++++++++++++++++++++++++++++++++++++++---------
9b550e
 test-cmdline.c |  11 ++
9b550e
 3 files changed, 354 insertions(+), 68 deletions(-)
9b550e
9b550e
diff --git a/ethtool.8.in b/ethtool.8.in
9b550e
index bb394cc..c1e6e09 100644
9b550e
--- a/ethtool.8.in
9b550e
+++ b/ethtool.8.in
9b550e
@@ -286,11 +286,12 @@ ethtool \- query or control network driver and hardware settings
9b550e
 .B ethtool \-T|\-\-show\-time\-stamping
9b550e
 .I devname
9b550e
 .HP
9b550e
-.B ethtool \-x|\-\-show\-rxfh\-indir
9b550e
+.B ethtool \-x|\-\-show\-rxfh\-indir|\-\-show\-rxfh
9b550e
 .I devname
9b550e
 .HP
9b550e
-.B ethtool \-X|\-\-set\-rxfh\-indir
9b550e
+.B ethtool \-X|\-\-set\-rxfh\-indir|\-\-rxfh
9b550e
 .I devname
9b550e
+.RB [ hkey \ \*(MA:\...]
9b550e
 .RB [\  equal
9b550e
 .IR N \ |
9b550e
 .BI weight\  W0
9b550e
@@ -784,11 +785,16 @@ Sets the dump flag for the device.
9b550e
 Show the device's time stamping capabilities and associated PTP
9b550e
 hardware clock.
9b550e
 .TP
9b550e
-.B \-x \-\-show\-rxfh\-indir
9b550e
-Retrieves the receive flow hash indirection table.
9b550e
+.B \-x \-\-show\-rxfh\-indir \-\-show\-rxfh
9b550e
+Retrieves the receive flow hash indirection table and/or RSS hash key.
9b550e
 .TP
9b550e
-.B \-X \-\-set\-rxfh\-indir
9b550e
-Configures the receive flow hash indirection table.
9b550e
+.B \-X \-\-set\-rxfh\-indir \-\-rxfh
9b550e
+Configures the receive flow hash indirection table and/or RSS hash key.
9b550e
+.TP
9b550e
+.BI hkey
9b550e
+Sets RSS hash key of the specified network device. RSS hash key should be of device supported length.
9b550e
+Hash key format must be in xx:yy:zz:aa:bb:cc format meaning both the nibbles of a byte should be mentioned
9b550e
+even if a nibble is zero.
9b550e
 .TP
9b550e
 .BI equal\  N
9b550e
 Sets the receive flow hash indirection table to spread flows evenly
9b550e
diff --git a/ethtool.c b/ethtool.c
9b550e
index 19b8b0c..bf583f3 100644
9b550e
--- a/ethtool.c
9b550e
+++ b/ethtool.c
9b550e
@@ -878,6 +878,74 @@ static char *unparse_rxfhashopts(u64 opts)
9b550e
 	return buf;
9b550e
 }
9b550e
 
9b550e
+static int convert_string_to_hashkey(char *rss_hkey, u32 key_size,
9b550e
+				     const char *rss_hkey_string)
9b550e
+{
9b550e
+	u32 i = 0;
9b550e
+	int hex_byte, len;
9b550e
+
9b550e
+	do {
9b550e
+		if (i > (key_size - 1)) {
9b550e
+			fprintf(stderr,
9b550e
+				"Key is too long for device (%u > %u)\n",
9b550e
+				i + 1, key_size);
9b550e
+			goto err;
9b550e
+		}
9b550e
+
9b550e
+		if (sscanf(rss_hkey_string, "%2x%n", &hex_byte, &len) < 1 ||
9b550e
+		    len != 2) {
9b550e
+			fprintf(stderr, "Invalid RSS hash key format\n");
9b550e
+			goto err;
9b550e
+		}
9b550e
+
9b550e
+		rss_hkey[i++] = hex_byte;
9b550e
+		rss_hkey_string += 2;
9b550e
+
9b550e
+		if (*rss_hkey_string == ':') {
9b550e
+			rss_hkey_string++;
9b550e
+		} else if (*rss_hkey_string != '\0') {
9b550e
+			fprintf(stderr, "Invalid RSS hash key format\n");
9b550e
+			goto err;
9b550e
+		}
9b550e
+
9b550e
+	} while (*rss_hkey_string);
9b550e
+
9b550e
+	if (i != key_size) {
9b550e
+		fprintf(stderr, "Key is too short for device (%u < %u)\n",
9b550e
+			i, key_size);
9b550e
+		goto err;
9b550e
+	}
9b550e
+
9b550e
+	return 0;
9b550e
+err:
9b550e
+	return 2;
9b550e
+}
9b550e
+
9b550e
+static int parse_hkey(char **rss_hkey, u32 key_size,
9b550e
+		      const char *rss_hkey_string)
9b550e
+{
9b550e
+	if (!key_size) {
9b550e
+		fprintf(stderr,
9b550e
+			"Cannot set RX flow hash configuration:\n"
9b550e
+			" Hash key setting not supported\n");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	*rss_hkey = malloc(key_size);
9b550e
+	if (!(*rss_hkey)) {
9b550e
+		perror("Cannot allocate memory for RSS hash key");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	if (convert_string_to_hashkey(*rss_hkey, key_size,
9b550e
+				      rss_hkey_string)) {
9b550e
+		free(*rss_hkey);
9b550e
+		*rss_hkey = NULL;
9b550e
+		return 2;
9b550e
+	}
9b550e
+	return 0;
9b550e
+}
9b550e
+
9b550e
 static const struct {
9b550e
 	const char *name;
9b550e
 	int (*func)(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
9b550e
@@ -3042,92 +3110,141 @@ static int do_grxclass(struct cmd_context *ctx)
9b550e
 	return err ? 1 : 0;
9b550e
 }
9b550e
 
9b550e
-static int do_grxfhindir(struct cmd_context *ctx)
9b550e
+static void print_indir_table(struct cmd_context *ctx,
9b550e
+			      struct ethtool_rxnfc *ring_count,
9b550e
+			      u32 indir_size, u32 *indir)
9b550e
 {
9b550e
-	struct ethtool_rxnfc ring_count;
9b550e
-	struct ethtool_rxfh_indir indir_head;
9b550e
-	struct ethtool_rxfh_indir *indir;
9b550e
 	u32 i;
9b550e
-	int err;
9b550e
 
9b550e
-	ring_count.cmd = ETHTOOL_GRXRINGS;
9b550e
-	err = send_ioctl(ctx, &ring_count);
9b550e
-	if (err < 0) {
9b550e
-		perror("Cannot get RX ring count");
9b550e
-		return 102;
9b550e
+	printf("RX flow hash indirection table for %s with %llu RX ring(s):\n",
9b550e
+	       ctx->devname, ring_count->data);
9b550e
+
9b550e
+	if (!indir_size)
9b550e
+		printf("Operation not supported\n");
9b550e
+
9b550e
+	for (i = 0; i < indir_size; i++) {
9b550e
+		if (i % 8 == 0)
9b550e
+			printf("%5u: ", i);
9b550e
+		printf(" %5u", indir[i]);
9b550e
+		if (i % 8 == 7)
9b550e
+			fputc('\n', stdout);
9b550e
 	}
9b550e
+}
9b550e
+
9b550e
+static int do_grxfhindir(struct cmd_context *ctx,
9b550e
+			 struct ethtool_rxnfc *ring_count)
9b550e
+{
9b550e
+	struct ethtool_rxfh_indir indir_head;
9b550e
+	struct ethtool_rxfh_indir *indir;
9b550e
+	int err;
9b550e
 
9b550e
 	indir_head.cmd = ETHTOOL_GRXFHINDIR;
9b550e
 	indir_head.size = 0;
9b550e
 	err = send_ioctl(ctx, &indir_head);
9b550e
 	if (err < 0) {
9b550e
 		perror("Cannot get RX flow hash indirection table size");
9b550e
-		return 103;
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
 	indir = malloc(sizeof(*indir) +
9b550e
 		       indir_head.size * sizeof(*indir->ring_index));
9b550e
+	if (!indir) {
9b550e
+		perror("Cannot allocate memory for indirection table");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
 	indir->cmd = ETHTOOL_GRXFHINDIR;
9b550e
 	indir->size = indir_head.size;
9b550e
 	err = send_ioctl(ctx, indir);
9b550e
 	if (err < 0) {
9b550e
 		perror("Cannot get RX flow hash indirection table");
9b550e
-		return 103;
9b550e
+		free(indir);
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
-	printf("RX flow hash indirection table for %s with %llu RX ring(s):\n",
9b550e
-	       ctx->devname, ring_count.data);
9b550e
-	for (i = 0; i < indir->size; i++) {
9b550e
-		if (i % 8 == 0)
9b550e
-			printf("%5u: ", i);
9b550e
-		printf(" %5u", indir->ring_index[i]);
9b550e
-		if (i % 8 == 7)
9b550e
-			fputc('\n', stdout);
9b550e
-	}
9b550e
+	print_indir_table(ctx, ring_count, indir->size, indir->ring_index);
9b550e
+
9b550e
+	free(indir);
9b550e
 	return 0;
9b550e
 }
9b550e
 
9b550e
-static int do_srxfhindir(struct cmd_context *ctx)
9b550e
+static int do_grxfh(struct cmd_context *ctx)
9b550e
 {
9b550e
-	int rxfhindir_equal = 0;
9b550e
-	char **rxfhindir_weight = NULL;
9b550e
-	struct ethtool_rxfh_indir indir_head;
9b550e
-	struct ethtool_rxfh_indir *indir;
9b550e
-	u32 i;
9b550e
+	struct ethtool_rxfh rss_head = {0};
9b550e
+	struct ethtool_rxnfc ring_count;
9b550e
+	struct ethtool_rxfh *rss;
9b550e
+	u32 i, indir_bytes;
9b550e
+	char *hkey;
9b550e
 	int err;
9b550e
 
9b550e
-	if (ctx->argc < 2)
9b550e
-		exit_bad_args();
9b550e
-	if (!strcmp(ctx->argp[0], "equal")) {
9b550e
-		if (ctx->argc != 2)
9b550e
-			exit_bad_args();
9b550e
-		rxfhindir_equal = get_int_range(ctx->argp[1], 0, 1, INT_MAX);
9b550e
-	} else if (!strcmp(ctx->argp[0], "weight")) {
9b550e
-		rxfhindir_weight = ctx->argp + 1;
9b550e
-	} else {
9b550e
-		exit_bad_args();
9b550e
+	ring_count.cmd = ETHTOOL_GRXRINGS;
9b550e
+	err = send_ioctl(ctx, &ring_count);
9b550e
+	if (err < 0) {
9b550e
+		perror("Cannot get RX ring count");
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
-	indir_head.cmd = ETHTOOL_GRXFHINDIR;
9b550e
-	indir_head.size = 0;
9b550e
-	err = send_ioctl(ctx, &indir_head);
9b550e
+	rss_head.cmd = ETHTOOL_GRSSH;
9b550e
+	err = send_ioctl(ctx, &rss_head);
9b550e
+	if (err < 0 && errno == EOPNOTSUPP) {
9b550e
+		return do_grxfhindir(ctx, &ring_count);
9b550e
+	} else if (err < 0) {
9b550e
+		perror("Cannot get RX flow hash indir size and/or key size");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	rss = calloc(1, sizeof(*rss) +
9b550e
+			rss_head.indir_size * sizeof(rss_head.rss_config[0]) +
9b550e
+			rss_head.key_size);
9b550e
+	if (!rss) {
9b550e
+		perror("Cannot allocate memory for RX flow hash config");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	rss->cmd = ETHTOOL_GRSSH;
9b550e
+	rss->indir_size = rss_head.indir_size;
9b550e
+	rss->key_size = rss_head.key_size;
9b550e
+	err = send_ioctl(ctx, rss);
9b550e
 	if (err < 0) {
9b550e
-		perror("Cannot get RX flow hash indirection table size");
9b550e
-		return 104;
9b550e
+		perror("Cannot get RX flow hash configuration");
9b550e
+		free(rss);
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
-	indir = malloc(sizeof(*indir) +
9b550e
-		       indir_head.size * sizeof(*indir->ring_index));
9b550e
-	indir->cmd = ETHTOOL_SRXFHINDIR;
9b550e
-	indir->size = indir_head.size;
9b550e
+	print_indir_table(ctx, &ring_count, rss->indir_size, rss->rss_config);
9b550e
+
9b550e
+	indir_bytes = rss->indir_size * sizeof(rss->rss_config[0]);
9b550e
+	hkey = ((char *)rss->rss_config + indir_bytes);
9b550e
 
9b550e
+	printf("RSS hash key:\n");
9b550e
+	if (!rss->key_size)
9b550e
+		printf("Operation not supported\n");
9b550e
+
9b550e
+	for (i = 0; i < rss->key_size; i++) {
9b550e
+		if (i == (rss->key_size - 1))
9b550e
+			printf("%02x\n", (u8) hkey[i]);
9b550e
+		else
9b550e
+			printf("%02x:", (u8) hkey[i]);
9b550e
+	}
9b550e
+
9b550e
+	free(rss);
9b550e
+	return 0;
9b550e
+}
9b550e
+
9b550e
+static int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_equal,
9b550e
+			    char **rxfhindir_weight, u32 num_weights)
9b550e
+{
9b550e
+	u32 i;
9b550e
+	/*
9b550e
+	 * "*indir_size == 0" ==> reset indir to default
9b550e
+	 */
9b550e
 	if (rxfhindir_equal) {
9b550e
-		for (i = 0; i < indir->size; i++)
9b550e
-			indir->ring_index[i] = i % rxfhindir_equal;
9b550e
-	} else {
9b550e
+		for (i = 0; i < *indir_size; i++)
9b550e
+			indir[i] = i % rxfhindir_equal;
9b550e
+	} else if (rxfhindir_weight) {
9b550e
 		u32 j, weight, sum = 0, partial = 0;
9b550e
 
9b550e
-		for (j = 0; rxfhindir_weight[j]; j++) {
9b550e
+		for (j = 0; j < num_weights; j++) {
9b550e
 			weight = get_u32(rxfhindir_weight[j], 0);
9b550e
 			sum += weight;
9b550e
 		}
9b550e
@@ -3135,36 +3252,187 @@ static int do_srxfhindir(struct cmd_context *ctx)
9b550e
 		if (sum == 0) {
9b550e
 			fprintf(stderr,
9b550e
 				"At least one weight must be non-zero\n");
9b550e
-			exit(1);
9b550e
+			return 2;
9b550e
 		}
9b550e
 
9b550e
-		if (sum > indir->size) {
9b550e
+		if (sum > *indir_size) {
9b550e
 			fprintf(stderr,
9b550e
 				"Total weight exceeds the size of the "
9b550e
 				"indirection table\n");
9b550e
-			exit(1);
9b550e
+			return 2;
9b550e
 		}
9b550e
 
9b550e
 		j = -1;
9b550e
-		for (i = 0; i < indir->size; i++) {
9b550e
-			while (i >= indir->size * partial / sum) {
9b550e
+		for (i = 0; i < *indir_size; i++) {
9b550e
+			while (i >= (*indir_size) * partial / sum) {
9b550e
 				j += 1;
9b550e
 				weight = get_u32(rxfhindir_weight[j], 0);
9b550e
 				partial += weight;
9b550e
 			}
9b550e
-			indir->ring_index[i] = j;
9b550e
+			indir[i] = j;
9b550e
 		}
9b550e
+	} else {
9b550e
+		*indir_size = ETH_RXFH_INDIR_NO_CHANGE;
9b550e
+	}
9b550e
+
9b550e
+	return 0;
9b550e
+}
9b550e
+
9b550e
+static int do_srxfhindir(struct cmd_context *ctx, int rxfhindir_equal,
9b550e
+			 char **rxfhindir_weight, u32 num_weights)
9b550e
+{
9b550e
+	struct ethtool_rxfh_indir indir_head;
9b550e
+	struct ethtool_rxfh_indir *indir;
9b550e
+	int err;
9b550e
+
9b550e
+	indir_head.cmd = ETHTOOL_GRXFHINDIR;
9b550e
+	indir_head.size = 0;
9b550e
+	err = send_ioctl(ctx, &indir_head);
9b550e
+	if (err < 0) {
9b550e
+		perror("Cannot get RX flow hash indirection table size");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	indir = malloc(sizeof(*indir) +
9b550e
+		       indir_head.size * sizeof(*indir->ring_index));
9b550e
+
9b550e
+	if (!indir) {
9b550e
+		perror("Cannot allocate memory for indirection table");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	indir->cmd = ETHTOOL_SRXFHINDIR;
9b550e
+	indir->size = indir_head.size;
9b550e
+
9b550e
+	if (fill_indir_table(&indir->size, indir->ring_index, rxfhindir_equal,
9b550e
+			     rxfhindir_weight, num_weights)) {
9b550e
+		free(indir);
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
 	err = send_ioctl(ctx, indir);
9b550e
 	if (err < 0) {
9b550e
 		perror("Cannot set RX flow hash indirection table");
9b550e
-		return 105;
9b550e
+		free(indir);
9b550e
+		return 1;
9b550e
 	}
9b550e
 
9b550e
+	free(indir);
9b550e
 	return 0;
9b550e
 }
9b550e
 
9b550e
+static int do_srxfh(struct cmd_context *ctx)
9b550e
+{
9b550e
+	struct ethtool_rxfh rss_head = {0};
9b550e
+	struct ethtool_rxfh *rss;
9b550e
+	struct ethtool_rxnfc ring_count;
9b550e
+	int rxfhindir_equal = 0;
9b550e
+	char **rxfhindir_weight = NULL;
9b550e
+	char *rxfhindir_key = NULL;
9b550e
+	char *hkey = NULL;
9b550e
+	int err = 0;
9b550e
+	u32 arg_num = 0, indir_bytes = 0;
9b550e
+	u32 entry_size = sizeof(rss_head.rss_config[0]);
9b550e
+	u32 num_weights = 0;
9b550e
+
9b550e
+	if (ctx->argc < 2)
9b550e
+		exit_bad_args();
9b550e
+
9b550e
+	while (arg_num < ctx->argc) {
9b550e
+		if (!strcmp(ctx->argp[arg_num], "equal")) {
9b550e
+			++arg_num;
9b550e
+			rxfhindir_equal = get_int_range(ctx->argp[arg_num],
9b550e
+							0, 1, INT_MAX);
9b550e
+			++arg_num;
9b550e
+		} else if (!strcmp(ctx->argp[arg_num], "weight")) {
9b550e
+			++arg_num;
9b550e
+			rxfhindir_weight = ctx->argp + arg_num;
9b550e
+			while (arg_num < ctx->argc &&
9b550e
+			       isdigit((unsigned char)ctx->argp[arg_num][0])) {
9b550e
+				++arg_num;
9b550e
+				++num_weights;
9b550e
+			}
9b550e
+			if (!num_weights)
9b550e
+				exit_bad_args();
9b550e
+		} else if (!strcmp(ctx->argp[arg_num], "hkey")) {
9b550e
+			++arg_num;
9b550e
+			rxfhindir_key = ctx->argp[arg_num];
9b550e
+			if (!rxfhindir_key)
9b550e
+				exit_bad_args();
9b550e
+			++arg_num;
9b550e
+		} else {
9b550e
+			exit_bad_args();
9b550e
+		}
9b550e
+	}
9b550e
+
9b550e
+	if (rxfhindir_equal && rxfhindir_weight) {
9b550e
+		fprintf(stderr,
9b550e
+			"Equal and weight options are mutually exclusive\n");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	ring_count.cmd = ETHTOOL_GRXRINGS;
9b550e
+	err = send_ioctl(ctx, &ring_count);
9b550e
+	if (err < 0) {
9b550e
+		perror("Cannot get RX ring count");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	rss_head.cmd = ETHTOOL_GRSSH;
9b550e
+	err = send_ioctl(ctx, &rss_head);
9b550e
+	if (err < 0 && errno == EOPNOTSUPP && !rxfhindir_key) {
9b550e
+		return do_srxfhindir(ctx, rxfhindir_equal, rxfhindir_weight,
9b550e
+				     num_weights);
9b550e
+	} else if (err < 0) {
9b550e
+		perror("Cannot get RX flow hash indir size and key size");
9b550e
+		return 1;
9b550e
+	}
9b550e
+
9b550e
+	if (rxfhindir_key) {
9b550e
+		err = parse_hkey(&hkey, rss_head.key_size,
9b550e
+				 rxfhindir_key);
9b550e
+		if (err)
9b550e
+			return err;
9b550e
+	}
9b550e
+
9b550e
+	if (rxfhindir_equal || rxfhindir_weight)
9b550e
+		indir_bytes = rss_head.indir_size * entry_size;
9b550e
+
9b550e
+	rss = calloc(1, sizeof(*rss) + indir_bytes + rss_head.key_size);
9b550e
+	if (!rss) {
9b550e
+		perror("Cannot allocate memory for RX flow hash config");
9b550e
+		return 1;
9b550e
+	}
9b550e
+	rss->cmd = ETHTOOL_SRSSH;
9b550e
+	rss->indir_size = rss_head.indir_size;
9b550e
+	rss->key_size = rss_head.key_size;
9b550e
+
9b550e
+	if (fill_indir_table(&rss->indir_size, rss->rss_config, rxfhindir_equal,
9b550e
+			     rxfhindir_weight, num_weights)) {
9b550e
+		err = 1;
9b550e
+		goto free;
9b550e
+	}
9b550e
+
9b550e
+	if (hkey)
9b550e
+		memcpy((char *)rss->rss_config + indir_bytes,
9b550e
+		       hkey, rss->key_size);
9b550e
+	else
9b550e
+		rss->key_size = 0;
9b550e
+
9b550e
+	err = send_ioctl(ctx, rss);
9b550e
+	if (err < 0) {
9b550e
+		perror("Cannot set RX flow hash configuration");
9b550e
+		err = 1;
9b550e
+	}
9b550e
+
9b550e
+free:
9b550e
+	if (hkey)
9b550e
+		free(hkey);
9b550e
+
9b550e
+	free(rss);
9b550e
+	return err;
9b550e
+}
9b550e
+
9b550e
 static int do_flash(struct cmd_context *ctx)
9b550e
 {
9b550e
 	char *flash_file;
9b550e
@@ -3842,11 +4110,12 @@ static const struct option {
9b550e
 	  "		delete %d\n" },
9b550e
 	{ "-T|--show-time-stamping", 1, do_tsinfo,
9b550e
 	  "Show time stamping capabilities" },
9b550e
-	{ "-x|--show-rxfh-indir", 1, do_grxfhindir,
9b550e
-	  "Show Rx flow hash indirection" },
9b550e
-	{ "-X|--set-rxfh-indir", 1, do_srxfhindir,
9b550e
-	  "Set Rx flow hash indirection",
9b550e
-	  "		equal N | weight W0 W1 ...\n" },
9b550e
+	{ "-x|--show-rxfh-indir|--show-rxfh", 1, do_grxfh,
9b550e
+	  "Show Rx flow hash indirection and/or hash key" },
9b550e
+	{ "-X|--set-rxfh-indir|--rxfh", 1, do_srxfh,
9b550e
+	  "Set Rx flow hash indirection and/or hash key",
9b550e
+	  "		[ equal N | weight W0 W1 ... ]\n"
9b550e
+	  "		[ hkey %x:%x:%x:%x:%x:.... ]\n" },
9b550e
 	{ "-f|--flash", 1, do_flash,
9b550e
 	  "Flash firmware image from the specified file to a region on the device",
9b550e
 	  "               FILENAME [ REGION-NUMBER-TO-FLASH ]\n" },
9b550e
diff --git a/test-cmdline.c b/test-cmdline.c
9b550e
index f1d4555..be41a30 100644
9b550e
--- a/test-cmdline.c
9b550e
+++ b/test-cmdline.c
9b550e
@@ -173,6 +173,7 @@ static struct test_case {
9b550e
 	{ 1, "-T" },
9b550e
 	{ 0, "-x devname" },
9b550e
 	{ 0, "--show-rxfh-indir devname" },
9b550e
+	{ 0, "--show-rxfh devname" },
9b550e
 	{ 1, "-x" },
9b550e
 	/* Argument parsing for -X is specialised */
9b550e
 	{ 0, "-X devname equal 2" },
9b550e
@@ -181,6 +182,16 @@ static struct test_case {
9b550e
 	{ 1, "--set-rxfh-indir devname equal foo" },
9b550e
 	{ 1, "-X devname equal" },
9b550e
 	{ 0, "--set-rxfh-indir devname weight 1 2 3 4" },
9b550e
+	{ 0, "--rxfh devname hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee" },
9b550e
+	{ 0, "-X devname hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee" },
9b550e
+	{ 1, "--rxfh devname hkey foo" },
9b550e
+	{ 1, "-X devname hkey foo" },
9b550e
+	{ 0, "--rxfh devname hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee weight 1 2 3 4" },
9b550e
+	{ 0, "-X devname weight 1 2 3 4 hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee" },
9b550e
+	{ 0, "--rxfh devname hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee equal 2" },
9b550e
+	{ 0, "-X devname equal 2 hkey 48:15:6e:bb:d8:bd:6f:b1:a4:c6:7a:c4:76:1c:29:98:da:e1:ae:6c:2e:12:2f:c0:b9:be:61:3d:00:54:35:9e:09:05:c7:d7:93:72:4a:ee" },
9b550e
+	{ 1, "--rxfh devname weight 1 2 3 4 equal 8" },
9b550e
+	{ 1, "-X devname weight 1 2 3 4 equal 8" },
9b550e
 	{ 1, "-X devname foo" },
9b550e
 	{ 1, "-X" },
9b550e
 	{ 0, "-P devname" },
9b550e
-- 
9b550e
1.8.3.1
9b550e