diff options
Diffstat (limited to 'lib/dynamic_debug.c')
| -rw-r--r-- | lib/dynamic_debug.c | 450 | 
1 files changed, 379 insertions, 71 deletions
| diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c index dd7f56af9aed..009f2ead09c1 100644 --- a/lib/dynamic_debug.c +++ b/lib/dynamic_debug.c @@ -41,9 +41,11 @@  extern struct _ddebug __start___dyndbg[];  extern struct _ddebug __stop___dyndbg[]; +extern struct ddebug_class_map __start___dyndbg_classes[]; +extern struct ddebug_class_map __stop___dyndbg_classes[];  struct ddebug_table { -	struct list_head link; +	struct list_head link, maps;  	const char *mod_name;  	unsigned int num_ddebugs;  	struct _ddebug *ddebugs; @@ -54,12 +56,13 @@ struct ddebug_query {  	const char *module;  	const char *function;  	const char *format; +	const char *class_string;  	unsigned int first_lineno, last_lineno;  };  struct ddebug_iter {  	struct ddebug_table *table; -	unsigned int idx; +	int idx;  };  struct flag_settings { @@ -134,15 +137,33 @@ static void vpr_info_dq(const struct ddebug_query *query, const char *msg)  			fmtlen--;  	} -	v3pr_info("%s: func=\"%s\" file=\"%s\" module=\"%s\" format=\"%.*s\" lineno=%u-%u\n", -		 msg, -		 query->function ?: "", -		 query->filename ?: "", -		 query->module ?: "", -		 fmtlen, query->format ?: "", -		 query->first_lineno, query->last_lineno); +	v3pr_info("%s: func=\"%s\" file=\"%s\" module=\"%s\" format=\"%.*s\" lineno=%u-%u class=%s\n", +		  msg, +		  query->function ?: "", +		  query->filename ?: "", +		  query->module ?: "", +		  fmtlen, query->format ?: "", +		  query->first_lineno, query->last_lineno, query->class_string);  } +static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table const *dt, +							  const char *class_string, int *class_id) +{ +	struct ddebug_class_map *map; +	int idx; + +	list_for_each_entry(map, &dt->maps, link) { +		idx = match_string(map->class_names, map->length, class_string); +		if (idx >= 0) { +			*class_id = idx + map->base; +			return map; +		} +	} +	*class_id = -ENOENT; +	return NULL; +} + +#define __outvar /* filled by callee */  /*   * Search the tables for _ddebug's which match the given `query' and   * apply the `flags' and `mask' to them.  Returns number of matching @@ -156,7 +177,9 @@ static int ddebug_change(const struct ddebug_query *query,  	struct ddebug_table *dt;  	unsigned int newflags;  	unsigned int nfound = 0; -	struct flagsbuf fbuf; +	struct flagsbuf fbuf, nbuf; +	struct ddebug_class_map *map = NULL; +	int __outvar valid_class;  	/* search for matching ddebugs */  	mutex_lock(&ddebug_lock); @@ -167,9 +190,22 @@ static int ddebug_change(const struct ddebug_query *query,  		    !match_wildcard(query->module, dt->mod_name))  			continue; +		if (query->class_string) { +			map = ddebug_find_valid_class(dt, query->class_string, &valid_class); +			if (!map) +				continue; +		} else { +			/* constrain query, do not touch class'd callsites */ +			valid_class = _DPRINTK_CLASS_DFLT; +		} +  		for (i = 0; i < dt->num_ddebugs; i++) {  			struct _ddebug *dp = &dt->ddebugs[i]; +			/* match site against query-class */ +			if (dp->class_id != valid_class) +				continue; +  			/* match against the source filename */  			if (query->filename &&  			    !match_wildcard(query->filename, dp->filename) && @@ -211,16 +247,18 @@ static int ddebug_change(const struct ddebug_query *query,  				continue;  #ifdef CONFIG_JUMP_LABEL  			if (dp->flags & _DPRINTK_FLAGS_PRINT) { -				if (!(modifiers->flags & _DPRINTK_FLAGS_PRINT)) +				if (!(newflags & _DPRINTK_FLAGS_PRINT))  					static_branch_disable(&dp->key.dd_key_true); -			} else if (modifiers->flags & _DPRINTK_FLAGS_PRINT) +			} else if (newflags & _DPRINTK_FLAGS_PRINT) {  				static_branch_enable(&dp->key.dd_key_true); +			}  #endif +			v4pr_info("changed %s:%d [%s]%s %s => %s\n", +				  trim_prefix(dp->filename), dp->lineno, +				  dt->mod_name, dp->function, +				  ddebug_describe_flags(dp->flags, &fbuf), +				  ddebug_describe_flags(newflags, &nbuf));  			dp->flags = newflags; -			v4pr_info("changed %s:%d [%s]%s =%s\n", -				 trim_prefix(dp->filename), dp->lineno, -				 dt->mod_name, dp->function, -				 ddebug_describe_flags(dp->flags, &fbuf));  		}  	}  	mutex_unlock(&ddebug_lock); @@ -383,10 +421,6 @@ static int ddebug_parse_query(char *words[], int nwords,  		return -EINVAL;  	} -	if (modname) -		/* support $modname.dyndbg=<multiple queries> */ -		query->module = modname; -  	for (i = 0; i < nwords; i += 2) {  		char *keyword = words[i];  		char *arg = words[i+1]; @@ -420,6 +454,8 @@ static int ddebug_parse_query(char *words[], int nwords,  		} else if (!strcmp(keyword, "line")) {  			if (parse_linerange(query, arg))  				return -EINVAL; +		} else if (!strcmp(keyword, "class")) { +			rc = check_set(&query->class_string, arg, "class");  		} else {  			pr_err("unknown keyword \"%s\"\n", keyword);  			return -EINVAL; @@ -427,6 +463,13 @@ static int ddebug_parse_query(char *words[], int nwords,  		if (rc)  			return rc;  	} +	if (!query->module && modname) +		/* +		 * support $modname.dyndbg=<multiple queries>, when +		 * not given in the query itself +		 */ +		query->module = modname; +  	vpr_info_dq(query, "parsed");  	return 0;  } @@ -553,34 +596,217 @@ static int ddebug_exec_queries(char *query, const char *modname)  	return nfound;  } +/* apply a new bitmap to the sys-knob's current bit-state */ +static int ddebug_apply_class_bitmap(const struct ddebug_class_param *dcp, +				     unsigned long *new_bits, unsigned long *old_bits) +{ +#define QUERY_SIZE 128 +	char query[QUERY_SIZE]; +	const struct ddebug_class_map *map = dcp->map; +	int matches = 0; +	int bi, ct; + +	v2pr_info("apply: 0x%lx to: 0x%lx\n", *new_bits, *old_bits); + +	for (bi = 0; bi < map->length; bi++) { +		if (test_bit(bi, new_bits) == test_bit(bi, old_bits)) +			continue; + +		snprintf(query, QUERY_SIZE, "class %s %c%s", map->class_names[bi], +			 test_bit(bi, new_bits) ? '+' : '-', dcp->flags); + +		ct = ddebug_exec_queries(query, NULL); +		matches += ct; + +		v2pr_info("bit_%d: %d matches on class: %s -> 0x%lx\n", bi, +			  ct, map->class_names[bi], *new_bits); +	} +	return matches; +} + +/* stub to later conditionally add "$module." prefix where not already done */ +#define KP_NAME(kp)	kp->name + +#define CLASSMAP_BITMASK(width) ((1UL << (width)) - 1) + +/* accept comma-separated-list of [+-] classnames */ +static int param_set_dyndbg_classnames(const char *instr, const struct kernel_param *kp) +{ +	const struct ddebug_class_param *dcp = kp->arg; +	const struct ddebug_class_map *map = dcp->map; +	unsigned long curr_bits, old_bits; +	char *cl_str, *p, *tmp; +	int cls_id, totct = 0; +	bool wanted; + +	cl_str = tmp = kstrdup(instr, GFP_KERNEL); +	p = strchr(cl_str, '\n'); +	if (p) +		*p = '\0'; + +	/* start with previously set state-bits, then modify */ +	curr_bits = old_bits = *dcp->bits; +	vpr_info("\"%s\" > %s:0x%lx\n", cl_str, KP_NAME(kp), curr_bits); + +	for (; cl_str; cl_str = p) { +		p = strchr(cl_str, ','); +		if (p) +			*p++ = '\0'; + +		if (*cl_str == '-') { +			wanted = false; +			cl_str++; +		} else { +			wanted = true; +			if (*cl_str == '+') +				cl_str++; +		} +		cls_id = match_string(map->class_names, map->length, cl_str); +		if (cls_id < 0) { +			pr_err("%s unknown to %s\n", cl_str, KP_NAME(kp)); +			continue; +		} + +		/* have one or more valid class_ids of one *_NAMES type */ +		switch (map->map_type) { +		case DD_CLASS_TYPE_DISJOINT_NAMES: +			/* the +/- pertains to a single bit */ +			if (test_bit(cls_id, &curr_bits) == wanted) { +				v3pr_info("no change on %s\n", cl_str); +				continue; +			} +			curr_bits ^= BIT(cls_id); +			totct += ddebug_apply_class_bitmap(dcp, &curr_bits, dcp->bits); +			*dcp->bits = curr_bits; +			v2pr_info("%s: changed bit %d:%s\n", KP_NAME(kp), cls_id, +				  map->class_names[cls_id]); +			break; +		case DD_CLASS_TYPE_LEVEL_NAMES: +			/* cls_id = N in 0..max. wanted +/- determines N or N-1 */ +			old_bits = CLASSMAP_BITMASK(*dcp->lvl); +			curr_bits = CLASSMAP_BITMASK(cls_id + (wanted ? 1 : 0 )); + +			totct += ddebug_apply_class_bitmap(dcp, &curr_bits, &old_bits); +			*dcp->lvl = (cls_id + (wanted ? 1 : 0)); +			v2pr_info("%s: changed bit-%d: \"%s\" %lx->%lx\n", KP_NAME(kp), cls_id, +				  map->class_names[cls_id], old_bits, curr_bits); +			break; +		default: +			pr_err("illegal map-type value %d\n", map->map_type); +		} +	} +	kfree(tmp); +	vpr_info("total matches: %d\n", totct); +	return 0; +} +  /** - * dynamic_debug_exec_queries - select and change dynamic-debug prints - * @query: query-string described in admin-guide/dynamic-debug-howto - * @modname: string containing module name, usually &module.mod_name + * param_set_dyndbg_classes - class FOO >control + * @instr: string echo>d to sysfs, input depends on map_type + * @kp:    kp->arg has state: bits/lvl, map, map_type + * + * Enable/disable prdbgs by their class, as given in the arguments to + * DECLARE_DYNDBG_CLASSMAP.  For LEVEL map-types, enforce relative + * levels by bitpos.   * - * This uses the >/proc/dynamic_debug/control reader, allowing module - * authors to modify their dynamic-debug callsites. The modname is - * canonically struct module.mod_name, but can also be null or a - * module-wildcard, for example: "drm*". + * Returns: 0 or <0 if error.   */ -int dynamic_debug_exec_queries(const char *query, const char *modname) +int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp)  { -	int rc; -	char *qry; /* writable copy of query */ - -	if (!query) { -		pr_err("non-null query/command string expected\n"); +	const struct ddebug_class_param *dcp = kp->arg; +	const struct ddebug_class_map *map = dcp->map; +	unsigned long inrep, new_bits, old_bits; +	int rc, totct = 0; + +	switch (map->map_type) { + +	case DD_CLASS_TYPE_DISJOINT_NAMES: +	case DD_CLASS_TYPE_LEVEL_NAMES: +		/* handle [+-]classnames list separately, we are done here */ +		return param_set_dyndbg_classnames(instr, kp); + +	case DD_CLASS_TYPE_DISJOINT_BITS: +	case DD_CLASS_TYPE_LEVEL_NUM: +		/* numeric input, accept and fall-thru */ +		rc = kstrtoul(instr, 0, &inrep); +		if (rc) { +			pr_err("expecting numeric input: %s > %s\n", instr, KP_NAME(kp)); +			return -EINVAL; +		} +		break; +	default: +		pr_err("%s: bad map type: %d\n", KP_NAME(kp), map->map_type);  		return -EINVAL;  	} -	qry = kstrndup(query, PAGE_SIZE, GFP_KERNEL); -	if (!qry) -		return -ENOMEM; -	rc = ddebug_exec_queries(qry, modname); -	kfree(qry); -	return rc; +	/* only _BITS,_NUM (numeric) map-types get here */ +	switch (map->map_type) { +	case DD_CLASS_TYPE_DISJOINT_BITS: +		/* expect bits. mask and warn if too many */ +		if (inrep & ~CLASSMAP_BITMASK(map->length)) { +			pr_warn("%s: input: 0x%lx exceeds mask: 0x%lx, masking\n", +				KP_NAME(kp), inrep, CLASSMAP_BITMASK(map->length)); +			inrep &= CLASSMAP_BITMASK(map->length); +		} +		v2pr_info("bits:%lx > %s\n", inrep, KP_NAME(kp)); +		totct += ddebug_apply_class_bitmap(dcp, &inrep, dcp->bits); +		*dcp->bits = inrep; +		break; +	case DD_CLASS_TYPE_LEVEL_NUM: +		/* input is bitpos, of highest verbosity to be enabled */ +		if (inrep > map->length) { +			pr_warn("%s: level:%ld exceeds max:%d, clamping\n", +				KP_NAME(kp), inrep, map->length); +			inrep = map->length; +		} +		old_bits = CLASSMAP_BITMASK(*dcp->lvl); +		new_bits = CLASSMAP_BITMASK(inrep); +		v2pr_info("lvl:%ld bits:0x%lx > %s\n", inrep, new_bits, KP_NAME(kp)); +		totct += ddebug_apply_class_bitmap(dcp, &new_bits, &old_bits); +		*dcp->lvl = inrep; +		break; +	default: +		pr_warn("%s: bad map type: %d\n", KP_NAME(kp), map->map_type); +	} +	vpr_info("%s: total matches: %d\n", KP_NAME(kp), totct); +	return 0; +} +EXPORT_SYMBOL(param_set_dyndbg_classes); + +/** + * param_get_dyndbg_classes - classes reader + * @buffer: string description of controlled bits -> classes + * @kp:     kp->arg has state: bits, map + * + * Reads last written state, underlying prdbg state may have been + * altered by direct >control.  Displays 0x for DISJOINT, 0-N for + * LEVEL Returns: #chars written or <0 on error + */ +int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp) +{ +	const struct ddebug_class_param *dcp = kp->arg; +	const struct ddebug_class_map *map = dcp->map; + +	switch (map->map_type) { + +	case DD_CLASS_TYPE_DISJOINT_NAMES: +	case DD_CLASS_TYPE_DISJOINT_BITS: +		return scnprintf(buffer, PAGE_SIZE, "0x%lx\n", *dcp->bits); + +	case DD_CLASS_TYPE_LEVEL_NAMES: +	case DD_CLASS_TYPE_LEVEL_NUM: +		return scnprintf(buffer, PAGE_SIZE, "%d\n", *dcp->lvl); +	default: +		return -1; +	}  } -EXPORT_SYMBOL_GPL(dynamic_debug_exec_queries); +EXPORT_SYMBOL(param_get_dyndbg_classes); + +const struct kernel_param_ops param_ops_dyndbg_classes = { +	.set = param_set_dyndbg_classes, +	.get = param_get_dyndbg_classes, +}; +EXPORT_SYMBOL(param_ops_dyndbg_classes);  #define PREFIX_SIZE 64 @@ -803,13 +1029,12 @@ static struct _ddebug *ddebug_iter_first(struct ddebug_iter *iter)  {  	if (list_empty(&ddebug_tables)) {  		iter->table = NULL; -		iter->idx = 0;  		return NULL;  	}  	iter->table = list_entry(ddebug_tables.next,  				 struct ddebug_table, link); -	iter->idx = 0; -	return &iter->table->ddebugs[iter->idx]; +	iter->idx = iter->table->num_ddebugs; +	return &iter->table->ddebugs[--iter->idx];  }  /* @@ -822,15 +1047,16 @@ static struct _ddebug *ddebug_iter_next(struct ddebug_iter *iter)  {  	if (iter->table == NULL)  		return NULL; -	if (++iter->idx == iter->table->num_ddebugs) { +	if (--iter->idx < 0) {  		/* iterate to next table */ -		iter->idx = 0;  		if (list_is_last(&iter->table->link, &ddebug_tables)) {  			iter->table = NULL;  			return NULL;  		}  		iter->table = list_entry(iter->table->link.next,  					 struct ddebug_table, link); +		iter->idx = iter->table->num_ddebugs; +		--iter->idx;  	}  	return &iter->table->ddebugs[iter->idx];  } @@ -876,6 +1102,20 @@ static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)  	return dp;  } +#define class_in_range(class_id, map)					\ +	(class_id >= map->base && class_id < map->base + map->length) + +static const char *ddebug_class_name(struct ddebug_iter *iter, struct _ddebug *dp) +{ +	struct ddebug_class_map *map; + +	list_for_each_entry(map, &iter->table->maps, link) +		if (class_in_range(dp->class_id, map)) +			return map->class_names[dp->class_id - map->base]; + +	return NULL; +} +  /*   * Seq_ops show method.  Called several times within a read()   * call from userspace, with ddebug_lock held.  Formats the @@ -887,6 +1127,7 @@ static int ddebug_proc_show(struct seq_file *m, void *p)  	struct ddebug_iter *iter = m->private;  	struct _ddebug *dp = p;  	struct flagsbuf flags; +	char const *class;  	if (p == SEQ_START_TOKEN) {  		seq_puts(m, @@ -898,8 +1139,17 @@ static int ddebug_proc_show(struct seq_file *m, void *p)  		   trim_prefix(dp->filename), dp->lineno,  		   iter->table->mod_name, dp->function,  		   ddebug_describe_flags(dp->flags, &flags)); -	seq_escape(m, dp->format, "\t\r\n\""); -	seq_puts(m, "\"\n"); +	seq_escape_str(m, dp->format, ESCAPE_SPACE, "\t\r\n\""); +	seq_puts(m, "\""); + +	if (dp->class_id != _DPRINTK_CLASS_DFLT) { +		class = ddebug_class_name(iter, dp); +		if (class) +			seq_printf(m, " class:%s", class); +		else +			seq_printf(m, " class unknown, _id:%d", dp->class_id); +	} +	seq_puts(m, "\n");  	return 0;  } @@ -943,18 +1193,50 @@ static const struct proc_ops proc_fops = {  	.proc_write = ddebug_proc_write  }; +static void ddebug_attach_module_classes(struct ddebug_table *dt, +					 struct ddebug_class_map *classes, +					 int num_classes) +{ +	struct ddebug_class_map *cm; +	int i, j, ct = 0; + +	for (cm = classes, i = 0; i < num_classes; i++, cm++) { + +		if (!strcmp(cm->mod_name, dt->mod_name)) { + +			v2pr_info("class[%d]: module:%s base:%d len:%d ty:%d\n", i, +				  cm->mod_name, cm->base, cm->length, cm->map_type); + +			for (j = 0; j < cm->length; j++) +				v3pr_info(" %d: %d %s\n", j + cm->base, j, +					  cm->class_names[j]); + +			list_add(&cm->link, &dt->maps); +			ct++; +		} +	} +	if (ct) +		vpr_info("module:%s attached %d classes\n", dt->mod_name, ct); +} +  /*   * Allocate a new ddebug_table for the given module   * and add it to the global list.   */ -int ddebug_add_module(struct _ddebug *tab, unsigned int n, -			     const char *name) +static int __ddebug_add_module(struct _ddebug_info *di, unsigned int base, +			       const char *modname)  {  	struct ddebug_table *dt; +	v3pr_info("add-module: %s.%d sites\n", modname, di->num_descs); +	if (!di->num_descs) { +		v3pr_info(" skip %s\n", modname); +		return 0; +	} +  	dt = kzalloc(sizeof(*dt), GFP_KERNEL);  	if (dt == NULL) { -		pr_err("error adding module: %s\n", name); +		pr_err("error adding module: %s\n", modname);  		return -ENOMEM;  	}  	/* @@ -963,18 +1245,29 @@ int ddebug_add_module(struct _ddebug *tab, unsigned int n,  	 * member of struct module, which lives at least as long as  	 * this struct ddebug_table.  	 */ -	dt->mod_name = name; -	dt->num_ddebugs = n; -	dt->ddebugs = tab; +	dt->mod_name = modname; +	dt->ddebugs = di->descs; +	dt->num_ddebugs = di->num_descs; + +	INIT_LIST_HEAD(&dt->link); +	INIT_LIST_HEAD(&dt->maps); + +	if (di->classes && di->num_classes) +		ddebug_attach_module_classes(dt, di->classes, di->num_classes);  	mutex_lock(&ddebug_lock); -	list_add(&dt->link, &ddebug_tables); +	list_add_tail(&dt->link, &ddebug_tables);  	mutex_unlock(&ddebug_lock); -	vpr_info("%3u debug prints in module %s\n", n, dt->mod_name); +	vpr_info("%3u debug prints in module %s\n", di->num_descs, modname);  	return 0;  } +int ddebug_add_module(struct _ddebug_info *di, const char *modname) +{ +	return __ddebug_add_module(di, 0, modname); +} +  /* helper for ddebug_dyndbg_(boot|module)_param_cb */  static int ddebug_dyndbg_param_cb(char *param, char *val,  				const char *modname, int on_err) @@ -1083,11 +1376,17 @@ static int __init dynamic_debug_init_control(void)  static int __init dynamic_debug_init(void)  { -	struct _ddebug *iter, *iter_start; -	const char *modname = NULL; +	struct _ddebug *iter, *iter_mod_start; +	int ret, i, mod_sites, mod_ct; +	const char *modname;  	char *cmdline; -	int ret = 0; -	int n = 0, entries = 0, modct = 0; + +	struct _ddebug_info di = { +		.descs = __start___dyndbg, +		.classes = __start___dyndbg_classes, +		.num_descs = __stop___dyndbg - __start___dyndbg, +		.num_classes = __stop___dyndbg_classes - __start___dyndbg_classes, +	};  	if (&__start___dyndbg == &__stop___dyndbg) {  		if (IS_ENABLED(CONFIG_DYNAMIC_DEBUG)) { @@ -1098,30 +1397,39 @@ static int __init dynamic_debug_init(void)  		ddebug_init_success = 1;  		return 0;  	} -	iter = __start___dyndbg; + +	iter = iter_mod_start = __start___dyndbg;  	modname = iter->modname; -	iter_start = iter; -	for (; iter < __stop___dyndbg; iter++) { -		entries++; +	i = mod_sites = mod_ct = 0; + +	for (; iter < __stop___dyndbg; iter++, i++, mod_sites++) { +  		if (strcmp(modname, iter->modname)) { -			modct++; -			ret = ddebug_add_module(iter_start, n, modname); +			mod_ct++; +			di.num_descs = mod_sites; +			di.descs = iter_mod_start; +			ret = __ddebug_add_module(&di, i - mod_sites, modname);  			if (ret)  				goto out_err; -			n = 0; + +			mod_sites = 0;  			modname = iter->modname; -			iter_start = iter; +			iter_mod_start = iter;  		} -		n++;  	} -	ret = ddebug_add_module(iter_start, n, modname); +	di.num_descs = mod_sites; +	di.descs = iter_mod_start; +	ret = __ddebug_add_module(&di, i - mod_sites, modname);  	if (ret)  		goto out_err;  	ddebug_init_success = 1;  	vpr_info("%d prdebugs in %d modules, %d KiB in ddebug tables, %d kiB in __dyndbg section\n", -		 entries, modct, (int)((modct * sizeof(struct ddebug_table)) >> 10), -		 (int)((entries * sizeof(struct _ddebug)) >> 10)); +		 i, mod_ct, (int)((mod_ct * sizeof(struct ddebug_table)) >> 10), +		 (int)((i * sizeof(struct _ddebug)) >> 10)); + +	if (di.num_classes) +		v2pr_info("  %d builtin ddebug class-maps\n", di.num_classes);  	/* now that ddebug tables are loaded, process all boot args  	 * again to find and activate queries given in dyndbg params. | 
