Implement basic percpu functionality

This piece allows to dump a percpu struct or union at a given address
on a given CPU.

Signed-off-by: Petr Tesarik <ptesarik@suse.cz>

---
 defs.h        |    2 
 global_data.c |    1 
 help.c        |   65 ++++++++++++++++++++++++
 symbols.c     |  156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 224 insertions(+)

--- a/defs.h
+++ b/defs.h
@@ -4124,6 +4124,7 @@ void cmd_sym(void);          /* symbols.
 void cmd_struct(void);       /* symbols.c */
 void cmd_union(void);        /* symbols.c */
 void cmd_pointer(void);      /* symbols.c */
+void cmd_percpu(void);       /* symbols.c */
 void cmd_whatis(void);       /* symbols.c */
 void cmd_p(void);            /* symbols.c */
 void cmd_mount(void);        /* filesys.c */
@@ -4608,6 +4609,7 @@ extern char *help_mod[];
 extern char *help_mount[];
 extern char *help_net[];
 extern char *help_p[];
+extern char *help_percpu[];
 extern char *help_ps[];
 extern char *help_pte[];
 extern char *help_ptob[];
--- a/global_data.c
+++ b/global_data.c
@@ -95,6 +95,7 @@ struct command_table_entry linux_command
 	{"mount",   cmd_mount,   help_mount,   0},
 	{"net",	    cmd_net,	help_net,      REFRESH_TASK_TABLE},
 	{"p",       cmd_p,       help_p,       0},
+	{"percpu",  cmd_percpu,  help_percpu,  0},
 	{"ps",      cmd_ps,      help_ps,      REFRESH_TASK_TABLE},
 	{"pte",     cmd_pte,     help_pte,     0},
 	{"ptob",    cmd_ptob,    help_ptob,    0},
--- a/help.c
+++ b/help.c
@@ -1147,6 +1147,71 @@ char *help_p[] = {
 NULL               
 };
 
+char *help_percpu[] = {
+"percpu",
+"percpu variables",
+"[-a] [-c cpu] [cpu]... [struct|union|*] struct_name [address|symbol]",
+"  This command displays a formatted display of the contents of a per-cpu",
+"  variable for a given set of CPUs.",
+" ",
+"  The set of CPUs can be specified in the following ways:\n",
+"        -a  show all CPUs.",
+"    -c cpu  specify cpu set, e.g. \"1,3,5\", \"1-3\", or \"1,3,5-7,10\".",
+"       cpu  show for a specific CPU (numeric only, can be repeated).",
+" ",
+"  If no CPU specifiers are present, percpu will use the CPU of the",
+"  currently selected task.",
+"\nEXAMPLES",
+"  Show the value of a per-cpu variable on processor 2:\n",
+"    %s> percpu 2 list_head blk_cpu_iopoll",
+"      [2]: ffff88011e290100",
+"    struct list_head {",
+"      next = 0xffff88011e290100, ",
+"      prev = 0xffff88011e290100",
+"    }",
+
+" ",
+"  Show a per-cpu variable on all processors:\n",
+"    %s> percpu -a disk_stats 0x1a468",
+"      [0]: ffff88011e21a468",
+"    struct disk_stats {",
+"      sectors = {11197190, 7550896}, ",
+"      ios = {360193, 159113}, ",
+"      merges = {11723, 22075}, ",
+"      ticks = {6180943, 11137498}, ",
+"      io_ticks = 1781827, ",
+"      time_in_queue = 16785949",
+"    }",
+"      [1]: ffff88011e25a468",
+"    struct disk_stats {",
+"      sectors = {1250132, 173032}, ",
+"      ios = {20393, 7573}, ",
+"      merges = {26161, 9947}, ",
+"      ticks = {415390, 758619}, ",
+"      io_ticks = 130165, ",
+"      time_in_queue = 1426936",
+"    }",
+"      [2]: ffff88011e29a468",
+"    struct disk_stats {",
+"      sectors = {88514, 10136}, ",
+"      ios = {1381, 382}, ",
+"      merges = {5515, 24870}, ",
+"      ticks = {29334, 69079}, ",
+"      io_ticks = 18280, ",
+"      time_in_queue = 264582",
+"    }",
+"      [3]: ffff88011e2da468",
+"    struct disk_stats {",
+"      sectors = {66608, 14128}, ",
+"      ios = {2731, 602}, ",
+"      merges = {4697, 5173}, ",
+"      ticks = {27411, 67344}, ",
+"      io_ticks = 13246, ",
+"      time_in_queue = 211221",
+"    }",
+NULL
+};
+
 char *help_ps[] = {
 "ps",
 "display process status information",
--- a/symbols.c
+++ b/symbols.c
@@ -5987,6 +5987,162 @@ freebuf:
 	}
 }
 
+/*
+ * This command displays a data type after adjusting the address with
+ * a per-cpu offset.
+ */
+void
+cmd_percpu(void)
+{
+	int c, i;
+	int cpulen;
+	ulong *cpus;
+	int cflag;
+	ulong flags;
+	char *structname, *typename;
+        struct datatype_member datatype_member, *dm = &datatype_member;
+	struct syment *sp;
+	ulong addr;
+	char buf[BUFSIZE];
+
+	cflag = 0;
+
+	if ((cpulen = STRUCT_SIZE("cpumask_t")) < 0)
+		cpulen = DIV_ROUND_UP(kt->cpus, BITS_PER_LONG) * sizeof(ulong);
+	cpus = (ulong *)GETBUF(cpulen);
+
+	while ((c = getopt(argcnt, args, "ac:")) != EOF) {
+		switch (c)
+		{
+		case 'a':
+			for (i = 0; i < kt->cpus; i++)
+				SET_BIT(cpus, i);
+			cflag = 1;
+			break;
+
+		case 'c':
+			make_cpumask(optarg, cpus, FAULT_ON_ERROR, NULL);
+			cflag = 1;
+			break;
+
+		default:
+			argerrs++;
+			break;
+		}
+	}
+
+	while (args[optind] && isdigit(args[optind][0])) {
+		int iserr = FALSE;
+		int cpu = dtoi(args[optind], RETURN_ON_ERROR, &iserr);
+		if (iserr) {
+			FREEBUF(cpus);
+			RESTART();
+		}
+		if (cpu >= kt->cpus) {
+			FREEBUF(cpus);
+			error(FATAL, "CPU %d is out of range\n", cpu);
+		}
+		SET_BIT(cpus, cpu);
+		cflag = 1;
+		++optind;
+	}
+
+	if (!cflag)
+		SET_BIT(cpus, CURRENT_CONTEXT()->processor);
+
+	flags = 0UL;
+	structname = NULL;
+
+	if (STREQ(args[optind], "struct")) {
+		flags |= STRUCT_REQUEST;
+		structname = args[++optind];
+	} else if (STREQ(args[optind], "union")) {
+		flags |= UNION_REQUEST;
+		structname = args[++optind];
+	} else if (args[optind][0] == '*') {
+		structname = args[optind][1]
+			? args[optind] + 1
+			: args[++optind];
+	} else {
+		structname = args[optind];
+	}
+	++optind;
+
+	if (argerrs || !structname || !args[optind] || args[optind+1]) {
+		FREEBUF(cpus);
+		cmd_usage(pc->curcmd, SYNOPSIS);
+	}
+
+	if (arg_to_datatype(structname, dm,
+			    DATATYPE_QUERY|ANON_MEMBER_QUERY|RETURN_ON_ERROR) < 1) {
+		FREEBUF(cpus);
+		error(FATAL, "invalid data structure reference: %s\n", structname);
+	}
+
+	if (dm->flags & TYPEDEF) {
+		flags |= dm->type;
+		typename = strdup(dm->name);
+	} else {
+		if (! (flags & (STRUCT_REQUEST|UNION_REQUEST)) )
+			flags |= dm->type;
+		typename = malloc(strlen(dm->name) + 8);
+		sprintf(typename, "%s %s",
+			dm->flags & UNION_REQUEST ? "union" : "struct",
+			dm->name);
+	}
+
+	if ((flags & (STRUCT_REQUEST|UNION_REQUEST)) !=
+	    dm->type & (STRUCT_REQUEST|UNION_REQUEST)) {
+		fprintf(fp, "data type mismatch: %s is not a %s\n",
+			structname,
+			flags & UNION_REQUEST ? "union" : "struct");
+		goto freebuf;
+	}
+
+	if (clean_arg() && IS_A_NUMBER(args[optind])) {
+		addr = htol(args[optind], FAULT_ON_ERROR, NULL);
+	} else if ((sp = per_cpu_symbol_search(args[optind]))) {
+		addr = sp->value;
+	} else {
+		if ((sp = symbol_search(args[optind]))) {
+			fprintf(fp, "not a per-cpu symbol: %s\n",
+				args[optind]);
+		} else {
+			fprintf(fp, "symbol not found: %s\n", args[optind]);
+			fprintf(fp, "possible alternatives:\n");
+			if (!symbol_query(args[optind], "  ", NULL))
+				fprintf(fp, "  (none found)\n");
+		}
+		goto freebuf;
+	}
+
+	for (i = 0; i < kt->cpus; i++) {
+		ulong cpuaddr;
+
+		if (!NUM_IN_BITMAP(cpus, i))
+			continue;
+
+		cpuaddr = addr + kt->__per_cpu_offset[i];
+		fprintf(fp, "  [%d]: %lx\n", i, cpuaddr);
+		if (!IS_KVADDR(cpuaddr)) {
+			error(WARNING,
+			      "invalid kernel virtual address: 0x%lx\n",
+			      cpuaddr);
+			continue;
+		}
+
+		fprintf(fp, "%s ", typename);
+		snprintf(buf, sizeof buf, "output *(%s*) 0x%lx",
+			 typename, cpuaddr);
+		gdb_pass_through(buf, NULL, GNU_RETURN_ON_ERROR);
+		fprintf(fp, "\n");
+	}
+
+freebuf:
+	free(typename);
+	FREEBUF(cpus);
+}
+
 
 /*
  *  Generic function for dumping data structure declarations, with a small
