Add shortened integer typedefs
[akaros.git] / kern / drivers / dev / vars.c
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
45 struct dev vars_devtab;
46
47 static char *devname(void)
48 {
49         return vars_devtab.name;
50 }
51
52 static struct dirtab *vars_dir;
53 static size_t nr_vars;
54 static qlock_t vars_lock;
55
56 struct dirtab __attribute__((__section__("devvars")))
57               __devvars_dot = {".", {0, 0, QTDIR}, 0, DMDIR | 0555};
58
59 DEVVARS_ENTRY(num_cores, "dw");
60
61 static 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. */
67 static 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
75 static 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
98 static 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
107 static 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
124 static 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
140 static 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
156 static void vars_close(struct chan *c)
157 {
158 }
159
160 static 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 */
169 static 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
252 static 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
295 static 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
400 static 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). */
411 static 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
435 struct 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
460 static char *s = "string";
461 static char c = 'x';
462 static uint8_t  _u8  = 8;
463 static uint16_t _u16 = 16;
464 static uint32_t _u32 = 32;
465 static uint64_t _u64 = 64;
466 static uint8_t  d8  = -8;
467 static uint16_t d16 = -16;
468 static uint32_t d32 = -32;
469 static uint64_t d64 = -64;
470 static uint8_t  x8  = 0x8;
471 static uint16_t x16 = 0x16;
472 static uint32_t x32 = 0x32;
473 static uint64_t x64 = 0x64;
474 static uint8_t  o8  = 01;
475 static uint16_t o16 = 016;
476 static uint32_t o32 = 032;
477 static uint64_t o64 = 064;
478
479 /* For testing creation.  There is no ENTRY for this. */
480 char *devvars_foobar = "foobar";
481
482 DEVVARS_ENTRY(s, "s");
483 DEVVARS_ENTRY(c, "c");
484 DEVVARS_ENTRY(_u8,  "ub");
485 DEVVARS_ENTRY(_u16, "uh");
486 DEVVARS_ENTRY(_u32, "uw");
487 DEVVARS_ENTRY(_u64, "ug");
488 DEVVARS_ENTRY(d8,  "db");
489 DEVVARS_ENTRY(d16, "dh");
490 DEVVARS_ENTRY(d32, "dw");
491 DEVVARS_ENTRY(d64, "dg");
492 DEVVARS_ENTRY(x8,  "xb");
493 DEVVARS_ENTRY(x16, "xh");
494 DEVVARS_ENTRY(x32, "xw");
495 DEVVARS_ENTRY(x64, "xg");
496 DEVVARS_ENTRY(o8,  "ob");
497 DEVVARS_ENTRY(o16, "oh");
498 DEVVARS_ENTRY(o32, "ow");
499 DEVVARS_ENTRY(o64, "og");
500
501 #endif /* CONFIG_DEVVARS_TEST */