| /* Copyright (c) 2015 Google Inc | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * #vars device, exports read access to select kernel variables.  These | 
 |  * variables are statically set. | 
 |  * | 
 |  * To add a variable, add a DEVVARS_ENTRY(name, format) somewhere in the kernel. | 
 |  * The format is a string consisting of two characters, using a modified version | 
 |  * of QEMU's formatting rules (ignoring count): [data_format][size] | 
 |  * | 
 |  * data_format is: | 
 |  *     x (hex) | 
 |  *     d (decimal) | 
 |  *     u (unsigned) | 
 |  *     o (octal) | 
 |  *     c (char)		does not need a size | 
 |  *     s (string)	does not need a size | 
 |  * size is: | 
 |  *     b (8 bits) | 
 |  *     h (16 bits) | 
 |  *     w (32 bits) | 
 |  *     g (64 bits) | 
 |  * | 
 |  * e.g. DEVVARS_ENTRY(num_cores, "dw"); | 
 |  * | 
 |  * Another thing we can consider doing is implementing create() to add variables | 
 |  * on the fly.  We can easily get the address (symbol table), but not the type, | 
 |  * unless we get debugging info.  We could consider a CTL command to allow the | 
 |  * user to change the type, though that might overload write() if we also allow | 
 |  * setting variables. */ | 
 |  | 
 | #include <ns.h> | 
 | #include <kmalloc.h> | 
 | #include <kref.h> | 
 | #include <atomic.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <error.h> | 
 | #include <sys/queue.h> | 
 | #include <fdtap.h> | 
 | #include <syscall.h> | 
 |  | 
 | struct dev vars_devtab; | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return vars_devtab.name; | 
 | } | 
 |  | 
 | static struct dirtab *vars_dir; | 
 | static size_t nr_vars; | 
 | static qlock_t vars_lock; | 
 |  | 
 | struct dirtab __attribute__((__section__("devvars"))) | 
 |               __devvars_dot = {".", {0, 0, QTDIR}, 0, DMDIR | 0555}; | 
 |  | 
 | DEVVARS_ENTRY(num_cores, "dw"); | 
 |  | 
 | static bool var_is_valid(struct dirtab *dir) | 
 | { | 
 | 	return dir->qid.vers != -1; | 
 | } | 
 |  | 
 | /* Careful with this.  c->name->s is the full path, at least sometimes. */ | 
 | static struct dirtab *find_var_by_name(const char *name) | 
 | { | 
 | 	for (size_t i = 0; i < nr_vars; i++) | 
 | 		if (!strcmp(vars_dir[i].name, name)) | 
 | 			return &vars_dir[i]; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void vars_init(void) | 
 | { | 
 | 	/* If you name a section without a '.', GCC will create start and stop | 
 | 	 * symbols, e.g. __start_SECTION */ | 
 | 	extern struct dirtab __start_devvars; | 
 | 	extern struct dirtab __stop_devvars; | 
 | 	struct dirtab *dot, temp; | 
 |  | 
 | 	nr_vars = &__stop_devvars - &__start_devvars; | 
 | 	vars_dir = kmalloc_array(nr_vars, sizeof(struct dirtab), MEM_WAIT); | 
 | 	if (!vars_dir) | 
 | 		error(ENOMEM, "kmalloc_array failed, nr_vars was %p", nr_vars); | 
 | 	memcpy(vars_dir, &__start_devvars, nr_vars * sizeof(struct dirtab)); | 
 | 	/* "." needs to be the first entry in a devtable.  It might already be | 
 | 	 * first, but we can do the swap regardless. */ | 
 | 	temp = vars_dir[0]; | 
 | 	dot = find_var_by_name("."); | 
 | 	assert(dot); | 
 | 	vars_dir[0] = *dot; | 
 | 	*dot = temp; | 
 | 	qlock_init(&vars_lock); | 
 | } | 
 |  | 
 | static struct chan *vars_attach(char *spec) | 
 | { | 
 | 	struct chan *c; | 
 |  | 
 | 	c = devattach(devname(), spec); | 
 | 	mkqid(&c->qid, 0, 0, QTDIR); | 
 | 	return c; | 
 | } | 
 |  | 
 | static struct walkqid *vars_walk(struct chan *c, struct chan *nc, char **name, | 
 | 				 unsigned int nname) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct walkqid *ret; | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&vars_lock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	ret = devwalk(c, nc, name, nname, vars_dir, nr_vars, devgen); | 
 | 	poperror(); | 
 | 	qunlock(&vars_lock); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static size_t vars_stat(struct chan *c, uint8_t *db, size_t n) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	size_t ret; | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&vars_lock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	ret = devstat(c, db, n, vars_dir, nr_vars, devgen); | 
 | 	poperror(); | 
 | 	qunlock(&vars_lock); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct chan *vars_open(struct chan *c, int omode) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct chan *ret; | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&vars_lock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	ret = devopen(c, omode, vars_dir, nr_vars, devgen); | 
 | 	poperror(); | 
 | 	qunlock(&vars_lock); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void vars_close(struct chan *c) | 
 | { | 
 | } | 
 |  | 
 | static struct dirtab *find_free_var(void) | 
 | { | 
 | 	for (size_t i = 0; i < nr_vars; i++) | 
 | 		if (!var_is_valid(&vars_dir[i])) | 
 | 			return &vars_dir[i]; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* We ignore the perm - they are all hard-coded in the dirtab */ | 
 | static void vars_create(struct chan *c, char *name, int omode, uint32_t perm, | 
 |                         char *ext) | 
 | { | 
 | 	struct dirtab *new_slot; | 
 | 	uintptr_t addr; | 
 | 	char *bang; | 
 | 	size_t size; | 
 |  | 
 | 	if (perm & DMSYMLINK) | 
 | 		error(EINVAL, "#%s doesn't support symlinks", devname()); | 
 | 	/* TODO: check that the user is privileged */ | 
 | 	bang = strchr(name, '!'); | 
 | 	if (!bang) | 
 | 		error(EINVAL, "Var %s has no ! in its format string", name); | 
 | 	*bang = 0; | 
 | 	addr = get_symbol_addr(name); | 
 | 	*bang = '!'; | 
 | 	if (!addr) | 
 | 		error(EINVAL, "Could not find symbol for %s", name); | 
 | 	bang++; | 
 | 	/* Note that we don't check the symbol type against the format.  We're | 
 | 	 * trusting the user here.  o/w we'd need dwarf support. */ | 
 | 	switch (*bang) { | 
 | 	case 'c': | 
 | 		size = sizeof(char); | 
 | 		break; | 
 | 	case 's': | 
 | 		size = sizeof(char*); | 
 | 		break; | 
 | 	case 'd': | 
 | 	case 'x': | 
 | 	case 'u': | 
 | 	case 'o': | 
 | 		bang++; | 
 | 		switch (*bang) { | 
 | 		case 'b': | 
 | 			size = sizeof(uint8_t); | 
 | 			break; | 
 | 		case 'h': | 
 | 			size = sizeof(uint16_t); | 
 | 			break; | 
 | 		case 'w': | 
 | 			size = sizeof(uint32_t); | 
 | 			break; | 
 | 		case 'g': | 
 | 			size = sizeof(uint64_t); | 
 | 			break; | 
 | 		default: | 
 | 			error(EINVAL, "Bad var size '%c'", *bang); | 
 | 		} | 
 | 		break; | 
 | 	default: | 
 | 		error(EINVAL, "Unknown var data_format '%c'", *bang); | 
 | 	} | 
 | 	bang++; | 
 | 	if (*bang) | 
 | 		error(EINVAL, "Extra chars for var %s", name); | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	new_slot = find_free_var(); | 
 | 	if (!new_slot) { | 
 | 		vars_dir = kreallocarray(vars_dir, nr_vars * 2, | 
 | 					 sizeof(struct dirtab), MEM_WAIT); | 
 | 		if (!vars_dir) | 
 | 			error(ENOMEM, "krealloc_array failed, nr_vars was %p", | 
 | 			      nr_vars); | 
 | 		memset(vars_dir + nr_vars, 0, nr_vars * sizeof(struct dirtab)); | 
 | 		for (size_t i = nr_vars; i < nr_vars * 2; i++) | 
 | 			vars_dir[i].qid.vers = -1; | 
 | 		new_slot = vars_dir + nr_vars; | 
 | 		nr_vars *= 2; | 
 | 	} | 
 | 	strlcpy(new_slot->name, name, sizeof(new_slot->name)); | 
 | 	new_slot->qid.path = addr; | 
 | 	new_slot->qid.vers = 0; | 
 | 	new_slot->qid.type = QTFILE; | 
 | 	new_slot->length = size; | 
 | 	new_slot->perm = 0444; | 
 | 	c->qid = new_slot->qid;		/* need to update c with its new qid */ | 
 | 	qunlock(&vars_lock); | 
 | 	c->mode = openmode(omode); | 
 | } | 
 |  | 
 | static const char *get_integer_fmt(char data_fmt, char data_size) | 
 | { | 
 | 	switch (data_fmt) { | 
 | 	case 'x': | 
 | 		switch (data_size) { | 
 | 		case 'b': | 
 | 		case 'h': | 
 | 		case 'w': | 
 | 			return "0x%x"; | 
 | 		case 'g': | 
 | 			return "0x%lx"; | 
 | 		} | 
 | 	case 'd': | 
 | 		switch (data_size) { | 
 | 		case 'b': | 
 | 		case 'h': | 
 | 		case 'w': | 
 | 			return "%d"; | 
 | 		case 'g': | 
 | 			return "%ld"; | 
 | 		} | 
 | 	case 'u': | 
 | 		switch (data_size) { | 
 | 		case 'b': | 
 | 		case 'h': | 
 | 		case 'w': | 
 | 			return "%u"; | 
 | 		case 'g': | 
 | 			return "%lu"; | 
 | 		} | 
 | 	case 'o': | 
 | 		switch (data_size) { | 
 | 		case 'b': | 
 | 		case 'h': | 
 | 		case 'w': | 
 | 			return "0%o"; | 
 | 		case 'g': | 
 | 			return "0%lo"; | 
 | 		} | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static size_t vars_read(struct chan *c, void *ubuf, size_t n, off64_t offset) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	char tmp[128];	/* big enough for any number and most strings */ | 
 | 	size_t size = sizeof(tmp); | 
 | 	char data_size, data_fmt, *fmt; | 
 | 	const char *fmt_int; | 
 | 	bool is_signed = FALSE; | 
 | 	size_t ret; | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&vars_lock); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	if (c->qid.type == QTDIR) { | 
 | 		ret = devdirread(c, ubuf, n, vars_dir, nr_vars, devgen); | 
 | 		poperror(); | 
 | 		qunlock(&vars_lock); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	/* These checks are mostly for the static variables.  They are a | 
 | 	 * double-check for the user-provided vars. */ | 
 | 	fmt = strchr(c->name->s, '!'); | 
 | 	if (!fmt) | 
 | 		error(EINVAL, "var %s has no ! in its format string", | 
 | 		      c->name->s); | 
 | 	fmt++; | 
 | 	data_fmt = *fmt; | 
 | 	if (!data_fmt) | 
 | 		error(EINVAL, "var %s has no data_format", c->name->s); | 
 |  | 
 | 	switch (data_fmt) { | 
 | 	case 'c': | 
 | 		size = snprintf(tmp, size, "%c", *(char*)c->qid.path); | 
 | 		break; | 
 | 	case 's': | 
 | 		size = snprintf(tmp, size, "%s", *(char**)c->qid.path); | 
 | 		break; | 
 | 	case 'd': | 
 | 		is_signed = TRUE; | 
 | 		/* fall through */ | 
 | 	case 'x': | 
 | 	case 'u': | 
 | 	case 'o': | 
 | 		fmt++; | 
 | 		data_size = *fmt; | 
 | 		if (!data_size) | 
 | 			error(EINVAL, "var %s has no size", c->name->s); | 
 | 		fmt_int = get_integer_fmt(data_fmt, data_size); | 
 | 		if (!fmt_int) | 
 | 			error(EINVAL, "#%s was unable to get an int fmt for %s", | 
 | 			      devname(), c->name->s); | 
 | 		switch (data_size) { | 
 | 		case 'b': | 
 | 			if (is_signed) | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(int8_t*)c->qid.path); | 
 | 			else | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(uint8_t*)c->qid.path); | 
 | 			break; | 
 | 		case 'h': | 
 | 			if (is_signed) | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(int16_t*)c->qid.path); | 
 | 			else | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(uint16_t*)c->qid.path); | 
 | 			break; | 
 | 		case 'w': | 
 | 			if (is_signed) | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(int32_t*)c->qid.path); | 
 | 			else | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(uint32_t*)c->qid.path); | 
 | 			break; | 
 | 		case 'g': | 
 | 			if (is_signed) | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(int64_t*)c->qid.path); | 
 | 			else | 
 | 				size = snprintf(tmp, size, fmt_int, | 
 | 						*(uint64_t*)c->qid.path); | 
 | 			break; | 
 | 		default: | 
 | 			error(EINVAL, "Bad #%s size %c", devname(), data_size); | 
 | 		} | 
 | 		break; | 
 | 	default: | 
 | 		error(EINVAL, "Unknown #%s data_format %c", devname(), | 
 | 		      data_fmt); | 
 | 	} | 
 | 	fmt++; | 
 | 	if (*fmt) | 
 | 		error(EINVAL, "Extra characters after var %s", c->name->s); | 
 | 	ret = readmem(offset, ubuf, n, tmp, size + 1); | 
 | 	poperror(); | 
 | 	qunlock(&vars_lock); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static size_t vars_write(struct chan *c, void *ubuf, size_t n, off64_t offset) | 
 | { | 
 | 	error(EFAIL, "Can't write to a #%s file", devname()); | 
 | } | 
 |  | 
 | /* remove is interesting.  we mark the qid in the dirtab as -1, which is a | 
 |  * signal to devgen that it is an invalid entry.  someone could already have | 
 |  * done a walk (before we qlocked) and grabbed the qid before it was -1.  as far | 
 |  * as they are concerned, they have a valid entry, since "the qid is the file" | 
 |  * for devvars.  (i.e. the chan gets a copy of the entire file, which fits into | 
 |  * the qid). */ | 
 | static void vars_remove(struct chan *c) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct dirtab *dir; | 
 | 	char *dir_name; | 
 |  | 
 | 	/* chan may have multiple elements in the path; get the last one. */ | 
 | 	dir_name = strrchr(c->name->s, '/'); | 
 | 	dir_name = dir_name ? dir_name + 1 : c->name->s; | 
 |  | 
 | 	qlock(&vars_lock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&vars_lock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	dir = find_var_by_name(dir_name); | 
 | 	if (!dir || dir->qid.vers == -1) | 
 | 		error(ENOENT, "Failed to remove %s, was it already removed?", | 
 | 		      c->name->s); | 
 | 	dir->qid.vers = -1; | 
 | 	poperror(); | 
 | 	qunlock(&vars_lock); | 
 | } | 
 |  | 
 | struct dev vars_devtab __devtab = { | 
 | 	.name = "vars", | 
 | 	.reset = devreset, | 
 | 	.init = vars_init, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = vars_attach, | 
 | 	.walk = vars_walk, | 
 | 	.stat = vars_stat, | 
 | 	.open = vars_open, | 
 | 	.create = vars_create, | 
 | 	.close = vars_close, | 
 | 	.read = vars_read, | 
 | 	.bread = devbread, | 
 | 	.write = vars_write, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = vars_remove, | 
 | 	.wstat = devwstat, | 
 | 	.power = devpower, | 
 | 	.chaninfo = devchaninfo, | 
 | 	.tapfd = 0, | 
 | }; | 
 |  | 
 | /* The utest needs these variables exported */ | 
 | #ifdef CONFIG_DEVVARS_TEST | 
 |  | 
 | static char *s = "string"; | 
 | static char c = 'x'; | 
 | static uint8_t  u8  = 8; | 
 | static uint16_t u16 = 16; | 
 | static uint32_t u32 = 32; | 
 | static uint64_t u64 = 64; | 
 | static uint8_t  d8  = -8; | 
 | static uint16_t d16 = -16; | 
 | static uint32_t d32 = -32; | 
 | static uint64_t d64 = -64; | 
 | static uint8_t  x8  = 0x8; | 
 | static uint16_t x16 = 0x16; | 
 | static uint32_t x32 = 0x32; | 
 | static uint64_t x64 = 0x64; | 
 | static uint8_t  o8  = 01; | 
 | static uint16_t o16 = 016; | 
 | static uint32_t o32 = 032; | 
 | static uint64_t o64 = 064; | 
 |  | 
 | /* For testing creation.  There is no ENTRY for this. */ | 
 | char *devvars_foobar = "foobar"; | 
 |  | 
 | DEVVARS_ENTRY(s, "s"); | 
 | DEVVARS_ENTRY(c, "c"); | 
 | DEVVARS_ENTRY(u8,  "ub"); | 
 | DEVVARS_ENTRY(u16, "uh"); | 
 | DEVVARS_ENTRY(u32, "uw"); | 
 | DEVVARS_ENTRY(u64, "ug"); | 
 | DEVVARS_ENTRY(d8,  "db"); | 
 | DEVVARS_ENTRY(d16, "dh"); | 
 | DEVVARS_ENTRY(d32, "dw"); | 
 | DEVVARS_ENTRY(d64, "dg"); | 
 | DEVVARS_ENTRY(x8,  "xb"); | 
 | DEVVARS_ENTRY(x16, "xh"); | 
 | DEVVARS_ENTRY(x32, "xw"); | 
 | DEVVARS_ENTRY(x64, "xg"); | 
 | DEVVARS_ENTRY(o8,  "ob"); | 
 | DEVVARS_ENTRY(o16, "oh"); | 
 | DEVVARS_ENTRY(o32, "ow"); | 
 | DEVVARS_ENTRY(o64, "og"); | 
 |  | 
 | #endif /* CONFIG_DEVVARS_TEST */ |