akaros/kern/drivers/dev/vars.c
<<
>>
Prefs
   1/* Copyright (c) 2015 Google Inc
   2 * Barret Rhoden <brho@cs.berkeley.edu>
   3 * See LICENSE for details.
   4 *
   5 * #vars device, exports read access to select kernel variables.  These
   6 * variables are statically set.
   7 *
   8 * To add a variable, add a DEVVARS_ENTRY(name, format) somewhere in the kernel.
   9 * The format is a string consisting of two characters, using a modified version
  10 * of QEMU's formatting rules (ignoring count): [data_format][size]
  11 *
  12 * data_format is:
  13 *     x (hex)
  14 *     d (decimal)
  15 *     u (unsigned)
  16 *     o (octal)
  17 *     c (char)         does not need a size
  18 *     s (string)       does not need a size
  19 * size is:
  20 *     b (8 bits)
  21 *     h (16 bits)
  22 *     w (32 bits)
  23 *     g (64 bits)
  24 *
  25 * e.g. DEVVARS_ENTRY(num_cores, "dw");
  26 *
  27 * Another thing we can consider doing is implementing create() to add variables
  28 * on the fly.  We can easily get the address (symbol table), but not the type,
  29 * unless we get debugging info.  We could consider a CTL command to allow the
  30 * user to change the type, though that might overload write() if we also allow
  31 * setting variables. */
  32
  33#include <ns.h>
  34#include <kmalloc.h>
  35#include <kref.h>
  36#include <atomic.h>
  37#include <string.h>
  38#include <stdio.h>
  39#include <assert.h>
  40#include <error.h>
  41#include <sys/queue.h>
  42#include <fdtap.h>
  43#include <syscall.h>
  44
  45struct dev vars_devtab;
  46
  47static char *devname(void)
  48{
  49        return vars_devtab.name;
  50}
  51
  52static struct dirtab *vars_dir;
  53static size_t nr_vars;
  54static qlock_t vars_lock;
  55
  56struct dirtab __attribute__((__section__("devvars")))
  57              __devvars_dot = {".", {0, 0, QTDIR}, 0, DMDIR | 0555};
  58
  59DEVVARS_ENTRY(num_cores, "dw");
  60
  61static bool var_is_valid(struct dirtab *dir)
  62{
  63        return dir->qid.vers != -1;
  64}
  65
  66/* Careful with this.  c->name->s is the full path, at least sometimes. */
  67static struct dirtab *find_var_by_name(const char *name)
  68{
  69        for (size_t i = 0; i < nr_vars; i++)
  70                if (!strcmp(vars_dir[i].name, name))
  71                        return &vars_dir[i];
  72        return 0;
  73}
  74
  75static void vars_init(void)
  76{
  77        /* If you name a section without a '.', GCC will create start and stop
  78         * symbols, e.g. __start_SECTION */
  79        extern struct dirtab __start_devvars;
  80        extern struct dirtab __stop_devvars;
  81        struct dirtab *dot, temp;
  82
  83        nr_vars = &__stop_devvars - &__start_devvars;
  84        vars_dir = kmalloc_array(nr_vars, sizeof(struct dirtab), MEM_WAIT);
  85        if (!vars_dir)
  86                error(ENOMEM, "kmalloc_array failed, nr_vars was %p", nr_vars);
  87        memcpy(vars_dir, &__start_devvars, nr_vars * sizeof(struct dirtab));
  88        /* "." needs to be the first entry in a devtable.  It might already be
  89         * first, but we can do the swap regardless. */
  90        temp = vars_dir[0];
  91        dot = find_var_by_name(".");
  92        assert(dot);
  93        vars_dir[0] = *dot;
  94        *dot = temp;
  95        qlock_init(&vars_lock);
  96}
  97
  98static struct chan *vars_attach(char *spec)
  99{
 100        struct chan *c;
 101
 102        c = devattach(devname(), spec);
 103        mkqid(&c->qid, 0, 0, QTDIR);
 104        return c;
 105}
 106
 107static struct walkqid *vars_walk(struct chan *c, struct chan *nc, char **name,
 108                                 unsigned int nname)
 109{
 110        ERRSTACK(1);
 111        struct walkqid *ret;
 112
 113        qlock(&vars_lock);
 114        if (waserror()) {
 115                qunlock(&vars_lock);
 116                nexterror();
 117        }
 118        ret = devwalk(c, nc, name, nname, vars_dir, nr_vars, devgen);
 119        poperror();
 120        qunlock(&vars_lock);
 121        return ret;
 122}
 123
 124static size_t vars_stat(struct chan *c, uint8_t *db, size_t n)
 125{
 126        ERRSTACK(1);
 127        size_t ret;
 128
 129        qlock(&vars_lock);
 130        if (waserror()) {
 131                qunlock(&vars_lock);
 132                nexterror();
 133        }
 134        ret = devstat(c, db, n, vars_dir, nr_vars, devgen);
 135        poperror();
 136        qunlock(&vars_lock);
 137        return ret;
 138}
 139
 140static struct chan *vars_open(struct chan *c, int omode)
 141{
 142        ERRSTACK(1);
 143        struct chan *ret;
 144
 145        qlock(&vars_lock);
 146        if (waserror()) {
 147                qunlock(&vars_lock);
 148                nexterror();
 149        }
 150        ret = devopen(c, omode, vars_dir, nr_vars, devgen);
 151        poperror();
 152        qunlock(&vars_lock);
 153        return ret;
 154}
 155
 156static void vars_close(struct chan *c)
 157{
 158}
 159
 160static struct dirtab *find_free_var(void)
 161{
 162        for (size_t i = 0; i < nr_vars; i++)
 163                if (!var_is_valid(&vars_dir[i]))
 164                        return &vars_dir[i];
 165        return 0;
 166}
 167
 168/* We ignore the perm - they are all hard-coded in the dirtab */
 169static void vars_create(struct chan *c, char *name, int omode, uint32_t perm,
 170                        char *ext)
 171{
 172        struct dirtab *new_slot;
 173        uintptr_t addr;
 174        char *bang;
 175        size_t size;
 176
 177        if (perm & DMSYMLINK)
 178                error(EINVAL, "#%s doesn't support symlinks", devname());
 179        /* TODO: check that the user is privileged */
 180        bang = strchr(name, '!');
 181        if (!bang)
 182                error(EINVAL, "Var %s has no ! in its format string", name);
 183        *bang = 0;
 184        addr = get_symbol_addr(name);
 185        *bang = '!';
 186        if (!addr)
 187                error(EINVAL, "Could not find symbol for %s", name);
 188        bang++;
 189        /* Note that we don't check the symbol type against the format.  We're
 190         * trusting the user here.  o/w we'd need dwarf support. */
 191        switch (*bang) {
 192        case 'c':
 193                size = sizeof(char);
 194                break;
 195        case 's':
 196                size = sizeof(char*);
 197                break;
 198        case 'd':
 199        case 'x':
 200        case 'u':
 201        case 'o':
 202                bang++;
 203                switch (*bang) {
 204                case 'b':
 205                        size = sizeof(uint8_t);
 206                        break;
 207                case 'h':
 208                        size = sizeof(uint16_t);
 209                        break;
 210                case 'w':
 211                        size = sizeof(uint32_t);
 212                        break;
 213                case 'g':
 214                        size = sizeof(uint64_t);
 215                        break;
 216                default:
 217                        error(EINVAL, "Bad var size '%c'", *bang);
 218                }
 219                break;
 220        default:
 221                error(EINVAL, "Unknown var data_format '%c'", *bang);
 222        }
 223        bang++;
 224        if (*bang)
 225                error(EINVAL, "Extra chars for var %s", name);
 226
 227        qlock(&vars_lock);
 228        new_slot = find_free_var();
 229        if (!new_slot) {
 230                vars_dir = kreallocarray(vars_dir, nr_vars * 2,
 231                                         sizeof(struct dirtab), MEM_WAIT);
 232                if (!vars_dir)
 233                        error(ENOMEM, "krealloc_array failed, nr_vars was %p",
 234                              nr_vars);
 235                memset(vars_dir + nr_vars, 0, nr_vars * sizeof(struct dirtab));
 236                for (size_t i = nr_vars; i < nr_vars * 2; i++)
 237                        vars_dir[i].qid.vers = -1;
 238                new_slot = vars_dir + nr_vars;
 239                nr_vars *= 2;
 240        }
 241        strlcpy(new_slot->name, name, sizeof(new_slot->name));
 242        new_slot->qid.path = addr;
 243        new_slot->qid.vers = 0;
 244        new_slot->qid.type = QTFILE;
 245        new_slot->length = size;
 246        new_slot->perm = 0444;
 247        c->qid = new_slot->qid;         /* need to update c with its new qid */
 248        qunlock(&vars_lock);
 249        c->mode = openmode(omode);
 250}
 251
 252static const char *get_integer_fmt(char data_fmt, char data_size)
 253{
 254        switch (data_fmt) {
 255        case 'x':
 256                switch (data_size) {
 257                case 'b':
 258                case 'h':
 259                case 'w':
 260                        return "0x%x";
 261                case 'g':
 262                        return "0x%lx";
 263                }
 264        case 'd':
 265                switch (data_size) {
 266                case 'b':
 267                case 'h':
 268                case 'w':
 269                        return "%d";
 270                case 'g':
 271                        return "%ld";
 272                }
 273        case 'u':
 274                switch (data_size) {
 275                case 'b':
 276                case 'h':
 277                case 'w':
 278                        return "%u";
 279                case 'g':
 280                        return "%lu";
 281                }
 282        case 'o':
 283                switch (data_size) {
 284                case 'b':
 285                case 'h':
 286                case 'w':
 287                        return "0%o";
 288                case 'g':
 289                        return "0%lo";
 290                }
 291        }
 292        return 0;
 293}
 294
 295static size_t vars_read(struct chan *c, void *ubuf, size_t n, off64_t offset)
 296{
 297        ERRSTACK(1);
 298        char tmp[128];  /* big enough for any number and most strings */
 299        size_t size = sizeof(tmp);
 300        char data_size, data_fmt, *fmt;
 301        const char *fmt_int;
 302        bool is_signed = FALSE;
 303        size_t ret;
 304
 305        qlock(&vars_lock);
 306        if (waserror()) {
 307                qunlock(&vars_lock);
 308                nexterror();
 309        }
 310
 311        if (c->qid.type == QTDIR) {
 312                ret = devdirread(c, ubuf, n, vars_dir, nr_vars, devgen);
 313                poperror();
 314                qunlock(&vars_lock);
 315                return ret;
 316        }
 317
 318        /* These checks are mostly for the static variables.  They are a
 319         * double-check for the user-provided vars. */
 320        fmt = strchr(c->name->s, '!');
 321        if (!fmt)
 322                error(EINVAL, "var %s has no ! in its format string",
 323                      c->name->s);
 324        fmt++;
 325        data_fmt = *fmt;
 326        if (!data_fmt)
 327                error(EINVAL, "var %s has no data_format", c->name->s);
 328
 329        switch (data_fmt) {
 330        case 'c':
 331                size = snprintf(tmp, size, "%c", *(char*)c->qid.path);
 332                break;
 333        case 's':
 334                size = snprintf(tmp, size, "%s", *(char**)c->qid.path);
 335                break;
 336        case 'd':
 337                is_signed = TRUE;
 338                /* fall through */
 339        case 'x':
 340        case 'u':
 341        case 'o':
 342                fmt++;
 343                data_size = *fmt;
 344                if (!data_size)
 345                        error(EINVAL, "var %s has no size", c->name->s);
 346                fmt_int = get_integer_fmt(data_fmt, data_size);
 347                if (!fmt_int)
 348                        error(EINVAL, "#%s was unable to get an int fmt for %s",
 349                              devname(), c->name->s);
 350                switch (data_size) {
 351                case 'b':
 352                        if (is_signed)
 353                                size = snprintf(tmp, size, fmt_int,
 354                                                *(int8_t*)c->qid.path);
 355                        else
 356                                size = snprintf(tmp, size, fmt_int,
 357                                                *(uint8_t*)c->qid.path);
 358                        break;
 359                case 'h':
 360                        if (is_signed)
 361                                size = snprintf(tmp, size, fmt_int,
 362                                                *(int16_t*)c->qid.path);
 363                        else
 364                                size = snprintf(tmp, size, fmt_int,
 365                                                *(uint16_t*)c->qid.path);
 366                        break;
 367                case 'w':
 368                        if (is_signed)
 369                                size = snprintf(tmp, size, fmt_int,
 370                                                *(int32_t*)c->qid.path);
 371                        else
 372                                size = snprintf(tmp, size, fmt_int,
 373                                                *(uint32_t*)c->qid.path);
 374                        break;
 375                case 'g':
 376                        if (is_signed)
 377                                size = snprintf(tmp, size, fmt_int,
 378                                                *(int64_t*)c->qid.path);
 379                        else
 380                                size = snprintf(tmp, size, fmt_int,
 381                                                *(uint64_t*)c->qid.path);
 382                        break;
 383                default:
 384                        error(EINVAL, "Bad #%s size %c", devname(), data_size);
 385                }
 386                break;
 387        default:
 388                error(EINVAL, "Unknown #%s data_format %c", devname(),
 389                      data_fmt);
 390        }
 391        fmt++;
 392        if (*fmt)
 393                error(EINVAL, "Extra characters after var %s", c->name->s);
 394        ret = readmem(offset, ubuf, n, tmp, size + 1);
 395        poperror();
 396        qunlock(&vars_lock);
 397        return ret;
 398}
 399
 400static size_t vars_write(struct chan *c, void *ubuf, size_t n, off64_t offset)
 401{
 402        error(EFAIL, "Can't write to a #%s file", devname());
 403}
 404
 405/* remove is interesting.  we mark the qid in the dirtab as -1, which is a
 406 * signal to devgen that it is an invalid entry.  someone could already have
 407 * done a walk (before we qlocked) and grabbed the qid before it was -1.  as far
 408 * as they are concerned, they have a valid entry, since "the qid is the file"
 409 * for devvars.  (i.e. the chan gets a copy of the entire file, which fits into
 410 * the qid). */
 411static void vars_remove(struct chan *c)
 412{
 413        ERRSTACK(1);
 414        struct dirtab *dir;
 415        char *dir_name;
 416
 417        /* chan may have multiple elements in the path; get the last one. */
 418        dir_name = strrchr(c->name->s, '/');
 419        dir_name = dir_name ? dir_name + 1 : c->name->s;
 420
 421        qlock(&vars_lock);
 422        if (waserror()) {
 423                qunlock(&vars_lock);
 424                nexterror();
 425        }
 426        dir = find_var_by_name(dir_name);
 427        if (!dir || dir->qid.vers == -1)
 428                error(ENOENT, "Failed to remove %s, was it already removed?",
 429                      c->name->s);
 430        dir->qid.vers = -1;
 431        poperror();
 432        qunlock(&vars_lock);
 433}
 434
 435struct dev vars_devtab __devtab = {
 436        .name = "vars",
 437        .reset = devreset,
 438        .init = vars_init,
 439        .shutdown = devshutdown,
 440        .attach = vars_attach,
 441        .walk = vars_walk,
 442        .stat = vars_stat,
 443        .open = vars_open,
 444        .create = vars_create,
 445        .close = vars_close,
 446        .read = vars_read,
 447        .bread = devbread,
 448        .write = vars_write,
 449        .bwrite = devbwrite,
 450        .remove = vars_remove,
 451        .wstat = devwstat,
 452        .power = devpower,
 453        .chaninfo = devchaninfo,
 454        .tapfd = 0,
 455};
 456
 457/* The utest needs these variables exported */
 458#ifdef CONFIG_DEVVARS_TEST
 459
 460static char *s = "string";
 461static char c = 'x';
 462static uint8_t  _u8  = 8;
 463static uint16_t _u16 = 16;
 464static uint32_t _u32 = 32;
 465static uint64_t _u64 = 64;
 466static uint8_t  d8  = -8;
 467static uint16_t d16 = -16;
 468static uint32_t d32 = -32;
 469static uint64_t d64 = -64;
 470static uint8_t  x8  = 0x8;
 471static uint16_t x16 = 0x16;
 472static uint32_t x32 = 0x32;
 473static uint64_t x64 = 0x64;
 474static uint8_t  o8  = 01;
 475static uint16_t o16 = 016;
 476static uint32_t o32 = 032;
 477static uint64_t o64 = 064;
 478
 479/* For testing creation.  There is no ENTRY for this. */
 480char *devvars_foobar = "foobar";
 481
 482DEVVARS_ENTRY(s, "s");
 483DEVVARS_ENTRY(c, "c");
 484DEVVARS_ENTRY(_u8,  "ub");
 485DEVVARS_ENTRY(_u16, "uh");
 486DEVVARS_ENTRY(_u32, "uw");
 487DEVVARS_ENTRY(_u64, "ug");
 488DEVVARS_ENTRY(d8,  "db");
 489DEVVARS_ENTRY(d16, "dh");
 490DEVVARS_ENTRY(d32, "dw");
 491DEVVARS_ENTRY(d64, "dg");
 492DEVVARS_ENTRY(x8,  "xb");
 493DEVVARS_ENTRY(x16, "xh");
 494DEVVARS_ENTRY(x32, "xw");
 495DEVVARS_ENTRY(x64, "xg");
 496DEVVARS_ENTRY(o8,  "ob");
 497DEVVARS_ENTRY(o16, "oh");
 498DEVVARS_ENTRY(o32, "ow");
 499DEVVARS_ENTRY(o64, "og");
 500
 501#endif /* CONFIG_DEVVARS_TEST */
 502