| /* Copyright (c) 2015 Google Inc | 
 |  * Davide Libenzi <dlibenzi@google.com> | 
 |  * See LICENSE for details. | 
 |  */ | 
 |  | 
 | #include <ros/common.h> | 
 | #include <ros/errno.h> | 
 | #include <smp.h> | 
 | #include <ns.h> | 
 | #include <kmalloc.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <err.h> | 
 | #include <build_info.h> | 
 |  | 
 | enum { | 
 | 	Kverdirqid = 0, | 
 | 	Kverbuildid, | 
 | 	Kverdate, | 
 | 	Kvercommitid, | 
 | 	Kverversion, | 
 | 	Kverversionname, | 
 | 	Kverkconfig, | 
 | 	BUILD_ID_SZ = 20, | 
 | 	BUILD_ID_OFFSET = 16, | 
 | }; | 
 |  | 
 | struct dev verdevtab; | 
 | static struct dirtab vertab[] = { | 
 | 	{".",		{Kverdirqid, 0, QTDIR}, 0,	DMDIR|0550}, | 
 | 	{"build_id",	{Kverbuildid},		0,	0444}, | 
 | 	{"date",	{Kverdate},		0,	0444}, | 
 | 	{"commitid",	{Kvercommitid},		0,	0444}, | 
 | 	{"version",	{Kverversion},		0,	0444}, | 
 | 	{"version_name",{Kverversionname},	0,	0444}, | 
 | 	{"kconfig",	{Kverkconfig},		0,	0444}, | 
 | }; | 
 |  | 
 | extern char __note_build_id_start[]; | 
 | extern char __note_build_id_end[]; | 
 |  | 
 | extern const char *__kconfig_str; | 
 |  | 
 | static char *get_build_id_start(void) | 
 | { | 
 | 	return __note_build_id_start + BUILD_ID_OFFSET; | 
 | } | 
 |  | 
 | static size_t build_id_sz(void) | 
 | { | 
 | 	return __note_build_id_end - get_build_id_start(); | 
 | } | 
 |  | 
 | static long ver_emit_nlstr(char *dest, const char *src, long size, | 
 | 						   long offset) | 
 | { | 
 | 	long n, slen = strlen(src); | 
 | 	char *buf = kmalloc(slen + 1, MEM_WAIT); | 
 |  | 
 | 	snprintf(buf, slen + 1, "%s", src); | 
 | 	n = readmem(offset, dest, size, buf, slen + 1); | 
 | 	kfree(buf); | 
 |  | 
 | 	return n; | 
 | } | 
 |  | 
 | static size_t ver_get_file_size(const char *src) | 
 | { | 
 | 	if (!src) | 
 | 		return 0; | 
 | 	return strlen(src) + 1; | 
 | } | 
 |  | 
 | static struct chan *ver_attach(char *spec) | 
 | { | 
 | 	return devattach(verdevtab.name, spec); | 
 | } | 
 |  | 
 | static void ver_init(void) | 
 | { | 
 | 	/* Our devtab's length params are wrong - need to stitch them up. */ | 
 | 	vertab[Kverbuildid].length = build_id_sz(); | 
 | 	vertab[Kverdate].length = ver_get_file_size(build_info_date); | 
 | 	vertab[Kvercommitid].length = ver_get_file_size(build_info_commitid); | 
 | 	vertab[Kverversion].length = ver_get_file_size(build_info_version); | 
 | 	vertab[Kverversionname].length = | 
 | 		ver_get_file_size(build_info_version_name); | 
 | 	vertab[Kverkconfig].length = strlen(__kconfig_str) + 1; | 
 | } | 
 |  | 
 | static struct walkqid *ver_walk(struct chan *c, struct chan *nc, char **name, | 
 | 				unsigned int nname) | 
 | { | 
 | 	return devwalk(c, nc, name, nname, vertab, ARRAY_SIZE(vertab), devgen); | 
 | } | 
 |  | 
 | static size_t ver_stat(struct chan *c, uint8_t *db, size_t n) | 
 | { | 
 | 	return devstat(c, db, n, vertab, ARRAY_SIZE(vertab), devgen); | 
 | } | 
 |  | 
 | static struct chan *ver_open(struct chan *c, int omode) | 
 | { | 
 | 	if (c->qid.type & QTDIR) { | 
 | 		if (openmode(omode) != O_READ) | 
 | 			error(EPERM, ERROR_FIXME); | 
 | 	} | 
 | 	c->mode = openmode(omode); | 
 | 	c->flag |= COPEN; | 
 | 	c->offset = 0; | 
 | 	return c; | 
 | } | 
 |  | 
 | static void ver_close(struct chan *c) | 
 | { | 
 | } | 
 |  | 
 | /* Returns a char representing the lowest 4 bits of x */ | 
 | static char num_to_nibble(unsigned int x) | 
 | { | 
 | 	return "0123456789abcdef"[x & 0xf]; | 
 | } | 
 |  | 
 | static ssize_t read_buildid(void *va, long n, off64_t off) | 
 | { | 
 | 	/* Each build_id byte needs 2 chars, and 1 for the \0 */ | 
 | 	char build_id[BUILD_ID_SZ * 2 + 1] = {0}; | 
 | 	uint8_t hi, lo; | 
 | 	uint8_t *b = (uint8_t*)get_build_id_start(); | 
 |  | 
 | 	for (int i = 0; i < BUILD_ID_SZ; i++) { | 
 | 		hi = *b >> 4; | 
 | 		lo = *b & 0xf; | 
 | 		build_id[i * 2 + 0] = num_to_nibble(hi); | 
 | 		build_id[i * 2 + 1] = num_to_nibble(lo); | 
 | 		b++; | 
 | 	} | 
 | 	return readmem(off, va, n, build_id, sizeof(build_id)); | 
 | } | 
 |  | 
 | static size_t ver_read(struct chan *c, void *va, size_t n, off64_t off) | 
 | { | 
 | 	switch ((int) c->qid.path) { | 
 | 	case Kverdirqid: | 
 | 		return devdirread(c, va, n, vertab, ARRAY_SIZE(vertab), devgen); | 
 | 	case Kverbuildid: | 
 | 		return read_buildid(va, n, off); | 
 | 	case Kverdate: | 
 | 		if (build_info_date) | 
 | 			return ver_emit_nlstr(va, build_info_date, n, | 
 | 					      (long) off); | 
 | 		break; | 
 | 	case Kvercommitid: | 
 | 		if (build_info_commitid) | 
 | 			return ver_emit_nlstr(va, build_info_commitid, n, | 
 | 					      (long) off); | 
 | 		break; | 
 | 	case Kverversion: | 
 | 		if (build_info_version) | 
 | 			return ver_emit_nlstr(va, build_info_version, n, | 
 | 					      (long) off); | 
 | 		break; | 
 | 	case Kverversionname: | 
 | 		if (build_info_version_name) | 
 | 			return ver_emit_nlstr(va, build_info_version_name, n, | 
 | 					      (long) off); | 
 | 		break; | 
 | 	case Kverkconfig: | 
 | 		return readstr(off, va, n, __kconfig_str); | 
 | 	default: | 
 | 		error(EINVAL, ERROR_FIXME); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static size_t ver_write(struct chan *c, void *a, size_t n, off64_t unused) | 
 | { | 
 | 	error(ENOTSUP, ERROR_FIXME); | 
 | 	return -1; | 
 | } | 
 |  | 
 | struct dev verdevtab __devtab = { | 
 | 	.name = "version", | 
 |  | 
 | 	.reset = devreset, | 
 | 	.init = ver_init, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = ver_attach, | 
 | 	.walk = ver_walk, | 
 | 	.stat = ver_stat, | 
 | 	.open = ver_open, | 
 | 	.create = devcreate, | 
 | 	.close = ver_close, | 
 | 	.read = ver_read, | 
 | 	.bread = devbread, | 
 | 	.write = ver_write, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = devremove, | 
 | 	.wstat = devwstat, | 
 | }; |