| /* 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 */ |