1 /*-
2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer,
10 * without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 * redistribution must be conditioned upon including a substantially
14 * similar Disclaimer requirement for further binary redistribution.
15 *
16 * NO WARRANTY
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 * THE POSSIBILITY OF SUCH DAMAGES.
28 */
29
30 #include <sys/cdefs.h>
31 #ifdef _KERNEL
32 #include <sys/param.h>
33 #include <sys/ctype.h>
34 #include <sys/limits.h>
35 #include <sys/malloc.h>
36 #include <sys/systm.h>
37 #else /* !_KERNEL */
38 #include <ctype.h>
39 #include <errno.h>
40 #include <stdint.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #endif /* _KERNEL */
45
46 #include "bhnd_nvram_private.h"
47
48 #include "bhnd_nvram_datavar.h"
49
50 #include "bhnd_nvram_data_tlvreg.h"
51
52 /*
53 * CFE TLV NVRAM data class.
54 *
55 * The CFE-defined TLV NVRAM format is used on the WGT634U.
56 */
57
58 struct bhnd_nvram_tlv {
59 struct bhnd_nvram_data nv; /**< common instance state */
60 struct bhnd_nvram_io *data; /**< backing buffer */
61 size_t count; /**< variable count */
62 };
63
64 BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
65 sizeof(struct bhnd_nvram_tlv))
66
67 /** Minimal TLV_ENV record header */
68 struct bhnd_nvram_tlv_env_hdr {
69 uint8_t tag;
70 uint8_t size;
71 } __packed;
72
73 /** Minimal TLV_ENV record */
74 struct bhnd_nvram_tlv_env {
75 struct bhnd_nvram_tlv_env_hdr hdr;
76 uint8_t flags;
77 char envp[];
78 } __packed;
79
80 /* Return the length in bytes of an TLV_ENV's envp data */
81 #define NVRAM_TLV_ENVP_DATA_LEN(_env) \
82 (((_env)->hdr.size < sizeof((_env)->flags)) ? 0 : \
83 ((_env)->hdr.size - sizeof((_env)->flags)))
84
85 /* Maximum supported length of the envp data field, in bytes */
86 #define NVRAM_TLV_ENVP_DATA_MAX_LEN \
87 (UINT8_MAX - sizeof(uint8_t) /* flags */)
88
89 static int bhnd_nvram_tlv_parse_size(
90 struct bhnd_nvram_io *io,
91 size_t *size);
92
93 static int bhnd_nvram_tlv_next_record(
94 struct bhnd_nvram_io *io,
95 size_t *next, size_t *offset,
96 uint8_t *tag);
97
98 static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_next_env(
99 struct bhnd_nvram_tlv *tlv,
100 size_t *next, void **cookiep);
101
102 static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_get_env(
103 struct bhnd_nvram_tlv *tlv,
104 void *cookiep);
105
106 static void *bhnd_nvram_tlv_to_cookie(
107 struct bhnd_nvram_tlv *tlv,
108 size_t io_offset);
109 static size_t bhnd_nvram_tlv_to_offset(
110 struct bhnd_nvram_tlv *tlv,
111 void *cookiep);
112
113 static int
bhnd_nvram_tlv_probe(struct bhnd_nvram_io * io)114 bhnd_nvram_tlv_probe(struct bhnd_nvram_io *io)
115 {
116 struct bhnd_nvram_tlv_env ident;
117 size_t nbytes;
118 int error;
119
120 nbytes = bhnd_nvram_io_getsize(io);
121
122 /* Handle what might be an empty TLV image */
123 if (nbytes < sizeof(ident)) {
124 uint8_t tag;
125
126 /* Fetch just the first tag */
127 error = bhnd_nvram_io_read(io, 0x0, &tag, sizeof(tag));
128 if (error)
129 return (error);
130
131 /* This *could* be an empty TLV image, but all we're
132 * testing for here is a single 0x0 byte followed by EOF */
133 if (tag == NVRAM_TLV_TYPE_END)
134 return (BHND_NVRAM_DATA_PROBE_MAYBE);
135
136 return (ENXIO);
137 }
138
139 /* Otherwise, look at the initial header for a valid TLV ENV tag,
140 * plus one byte of the entry data */
141 error = bhnd_nvram_io_read(io, 0x0, &ident,
142 sizeof(ident) + sizeof(ident.envp[0]));
143 if (error)
144 return (error);
145
146 /* First entry should be a variable record (which we statically
147 * assert as being defined to use a single byte size field) */
148 if (ident.hdr.tag != NVRAM_TLV_TYPE_ENV)
149 return (ENXIO);
150
151 _Static_assert(NVRAM_TLV_TYPE_ENV & NVRAM_TLV_TF_U8_LEN,
152 "TYPE_ENV is not a U8-sized field");
153
154 /* The entry must be at least 3 characters ('x=\0') in length */
155 if (ident.hdr.size < 3)
156 return (ENXIO);
157
158 /* The first character should be a valid key char (alpha) */
159 if (!bhnd_nv_isalpha(ident.envp[0]))
160 return (ENXIO);
161
162 return (BHND_NVRAM_DATA_PROBE_DEFAULT);
163 }
164
165 static int
bhnd_nvram_tlv_getvar_direct(struct bhnd_nvram_io * io,const char * name,void * buf,size_t * len,bhnd_nvram_type type)166 bhnd_nvram_tlv_getvar_direct(struct bhnd_nvram_io *io, const char *name,
167 void *buf, size_t *len, bhnd_nvram_type type)
168 {
169 struct bhnd_nvram_tlv_env env;
170 char data[NVRAM_TLV_ENVP_DATA_MAX_LEN];
171 size_t data_len;
172 const char *key, *value;
173 size_t keylen, vlen;
174 size_t namelen;
175 size_t next, off;
176 uint8_t tag;
177 int error;
178
179 namelen = strlen(name);
180
181 /* Iterate over the input looking for the requested variable */
182 next = 0;
183 while (!(error = bhnd_nvram_tlv_next_record(io, &next, &off, &tag))) {
184 switch (tag) {
185 case NVRAM_TLV_TYPE_END:
186 /* Not found */
187 return (ENOENT);
188
189 case NVRAM_TLV_TYPE_ENV:
190 /* Read the record header */
191 error = bhnd_nvram_io_read(io, off, &env, sizeof(env));
192 if (error) {
193 BHND_NV_LOG("error reading TLV_ENV record "
194 "header: %d\n", error);
195 return (error);
196 }
197
198 /* Read the record data */
199 data_len = NVRAM_TLV_ENVP_DATA_LEN(&env);
200 error = bhnd_nvram_io_read(io, off + sizeof(env), data,
201 data_len);
202 if (error) {
203 BHND_NV_LOG("error reading TLV_ENV record "
204 "data: %d\n", error);
205 return (error);
206 }
207
208 /* Parse the key=value string */
209 error = bhnd_nvram_parse_env(data, data_len, '=', &key,
210 &keylen, &value, &vlen);
211 if (error) {
212 BHND_NV_LOG("error parsing TLV_ENV data: %d\n",
213 error);
214 return (error);
215 }
216
217 /* Match against requested variable name */
218 if (keylen == namelen &&
219 strncmp(key, name, namelen) == 0)
220 {
221 return (bhnd_nvram_value_coerce(value, vlen,
222 BHND_NVRAM_TYPE_STRING, buf, len, type));
223 }
224
225 break;
226
227 default:
228 /* Skip unknown tags */
229 break;
230 }
231 }
232
233 /* Hit I/O error */
234 return (error);
235 }
236
237 static int
bhnd_nvram_tlv_serialize(bhnd_nvram_data_class * cls,bhnd_nvram_plist * props,bhnd_nvram_plist * options,void * outp,size_t * olen)238 bhnd_nvram_tlv_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
239 bhnd_nvram_plist *options, void *outp, size_t *olen)
240 {
241 bhnd_nvram_prop *prop;
242 size_t limit, nbytes;
243 int error;
244
245 /* Determine output byte limit */
246 if (outp != NULL)
247 limit = *olen;
248 else
249 limit = 0;
250
251 nbytes = 0;
252
253 /* Write all properties */
254 prop = NULL;
255 while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
256 struct bhnd_nvram_tlv_env env;
257 const char *name;
258 uint8_t *p;
259 size_t name_len, value_len;
260 size_t rec_size;
261
262 env.hdr.tag = NVRAM_TLV_TYPE_ENV;
263 env.hdr.size = sizeof(env.flags);
264 env.flags = 0x0;
265
266 /* Fetch name value and add to record length */
267 name = bhnd_nvram_prop_name(prop);
268 name_len = strlen(name) + 1 /* '=' */;
269
270 if (UINT8_MAX - env.hdr.size < name_len) {
271 BHND_NV_LOG("%s name exceeds maximum TLV record "
272 "length\n", name);
273 return (EFTYPE); /* would overflow TLV size */
274 }
275
276 env.hdr.size += name_len;
277
278 /* Add string value to record length */
279 error = bhnd_nvram_prop_encode(prop, NULL, &value_len,
280 BHND_NVRAM_TYPE_STRING);
281 if (error) {
282 BHND_NV_LOG("error serializing %s to required type "
283 "%s: %d\n", name,
284 bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
285 error);
286 return (error);
287 }
288
289 if (UINT8_MAX - env.hdr.size < value_len) {
290 BHND_NV_LOG("%s value exceeds maximum TLV record "
291 "length\n", name);
292 return (EFTYPE); /* would overflow TLV size */
293 }
294
295 env.hdr.size += value_len;
296
297 /* Calculate total record size */
298 rec_size = sizeof(env.hdr) + env.hdr.size;
299 if (SIZE_MAX - nbytes < rec_size)
300 return (EFTYPE); /* would overflow size_t */
301
302 /* Calculate our output pointer */
303 if (nbytes > limit || limit - nbytes < rec_size) {
304 /* buffer is full; cannot write */
305 p = NULL;
306 } else {
307 p = (uint8_t *)outp + nbytes;
308 }
309
310 /* Write to output */
311 if (p != NULL) {
312 memcpy(p, &env, sizeof(env));
313 p += sizeof(env);
314
315 memcpy(p, name, name_len - 1);
316 p[name_len - 1] = '=';
317 p += name_len;
318
319 error = bhnd_nvram_prop_encode(prop, p, &value_len,
320 BHND_NVRAM_TYPE_STRING);
321 if (error) {
322 BHND_NV_LOG("error serializing %s to required "
323 "type %s: %d\n", name,
324 bhnd_nvram_type_name(
325 BHND_NVRAM_TYPE_STRING),
326 error);
327 return (error);
328 }
329 }
330
331 nbytes += rec_size;
332 }
333
334 /* Write terminating END record */
335 if (limit > nbytes)
336 *((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END;
337
338 if (nbytes == SIZE_MAX)
339 return (EFTYPE); /* would overflow size_t */
340 nbytes++;
341
342 /* Provide required length */
343 *olen = nbytes;
344 if (limit < *olen) {
345 if (outp == NULL)
346 return (0);
347
348 return (ENOMEM);
349 }
350
351 return (0);
352 }
353
354 /**
355 * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src.
356 *
357 * @param tlv A newly allocated data instance.
358 */
359 static int
bhnd_nvram_tlv_init(struct bhnd_nvram_tlv * tlv,struct bhnd_nvram_io * src)360 bhnd_nvram_tlv_init(struct bhnd_nvram_tlv *tlv, struct bhnd_nvram_io *src)
361 {
362 struct bhnd_nvram_tlv_env *env;
363 size_t size;
364 size_t next;
365 int error;
366
367 BHND_NV_ASSERT(tlv->data == NULL, ("tlv data already initialized"));
368
369 /* Determine the actual size of the TLV source data */
370 if ((error = bhnd_nvram_tlv_parse_size(src, &size)))
371 return (error);
372
373 /* Copy to our own internal buffer */
374 if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL)
375 return (ENOMEM);
376
377 /* Initialize our backing buffer */
378 tlv->count = 0;
379 next = 0;
380 while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) {
381 size_t env_len;
382 size_t name_len;
383
384 /* TLV_ENV data must not be empty */
385 env_len = NVRAM_TLV_ENVP_DATA_LEN(env);
386 if (env_len == 0) {
387 BHND_NV_LOG("cannot parse zero-length TLV_ENV record "
388 "data\n");
389 return (EINVAL);
390 }
391
392 /* Parse the key=value string, and then replace the '='
393 * delimiter with '\0' to allow us to provide direct
394 * name pointers from our backing buffer */
395 error = bhnd_nvram_parse_env(env->envp, env_len, '=', NULL,
396 &name_len, NULL, NULL);
397 if (error) {
398 BHND_NV_LOG("error parsing TLV_ENV data: %d\n", error);
399 return (error);
400 }
401
402 /* Replace '=' with '\0' */
403 *(env->envp + name_len) = '\0';
404
405 /* Add to variable count */
406 tlv->count++;
407 };
408
409 return (0);
410 }
411
412 static int
bhnd_nvram_tlv_new(struct bhnd_nvram_data * nv,struct bhnd_nvram_io * io)413 bhnd_nvram_tlv_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
414 {
415
416 struct bhnd_nvram_tlv *tlv;
417 int error;
418
419 /* Allocate and initialize the TLV data instance */
420 tlv = (struct bhnd_nvram_tlv *)nv;
421
422 /* Parse the TLV input data and initialize our backing
423 * data representation */
424 if ((error = bhnd_nvram_tlv_init(tlv, io))) {
425 bhnd_nvram_tlv_free(nv);
426 return (error);
427 }
428
429 return (0);
430 }
431
432 static void
bhnd_nvram_tlv_free(struct bhnd_nvram_data * nv)433 bhnd_nvram_tlv_free(struct bhnd_nvram_data *nv)
434 {
435 struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
436 if (tlv->data != NULL)
437 bhnd_nvram_io_free(tlv->data);
438 }
439
440 size_t
bhnd_nvram_tlv_count(struct bhnd_nvram_data * nv)441 bhnd_nvram_tlv_count(struct bhnd_nvram_data *nv)
442 {
443 struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
444 return (tlv->count);
445 }
446
447 static bhnd_nvram_plist *
bhnd_nvram_tlv_options(struct bhnd_nvram_data * nv)448 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
449 {
450 return (NULL);
451 }
452
453 static uint32_t
bhnd_nvram_tlv_caps(struct bhnd_nvram_data * nv)454 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
455 {
456 return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
457 }
458
459 static const char *
bhnd_nvram_tlv_next(struct bhnd_nvram_data * nv,void ** cookiep)460 bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep)
461 {
462 struct bhnd_nvram_tlv *tlv;
463 struct bhnd_nvram_tlv_env *env;
464 size_t io_offset;
465
466 tlv = (struct bhnd_nvram_tlv *)nv;
467
468 /* Find next readable TLV record */
469 if (*cookiep == NULL) {
470 /* Start search at offset 0x0 */
471 io_offset = 0x0;
472 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
473 } else {
474 /* Seek past the previous env record */
475 io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep);
476 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL);
477 if (env == NULL)
478 BHND_NV_PANIC("invalid cookiep; record missing");
479
480 /* Advance to next env record, update the caller's cookiep */
481 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
482 }
483
484 /* Check for EOF */
485 if (env == NULL)
486 return (NULL);
487
488 /* Return the NUL terminated name */
489 return (env->envp);
490 }
491
492 static void *
bhnd_nvram_tlv_find(struct bhnd_nvram_data * nv,const char * name)493 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
494 {
495 return (bhnd_nvram_data_generic_find(nv, name));
496 }
497
498 static int
bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data * nv,void * cookiep1,void * cookiep2)499 bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
500 void *cookiep2)
501 {
502 if (cookiep1 < cookiep2)
503 return (-1);
504
505 if (cookiep1 > cookiep2)
506 return (1);
507
508 return (0);
509 }
510
511 static int
bhnd_nvram_tlv_getvar(struct bhnd_nvram_data * nv,void * cookiep,void * buf,size_t * len,bhnd_nvram_type type)512 bhnd_nvram_tlv_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
513 size_t *len, bhnd_nvram_type type)
514 {
515 return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
516 }
517
518 static int
bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data * nv,void * cookiep,bhnd_nvram_val ** value)519 bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
520 bhnd_nvram_val **value)
521 {
522 return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
523 }
524
525 static const void *
bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data * nv,void * cookiep,size_t * len,bhnd_nvram_type * type)526 bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
527 size_t *len, bhnd_nvram_type *type)
528 {
529 struct bhnd_nvram_tlv *tlv;
530 struct bhnd_nvram_tlv_env *env;
531 const char *val;
532 int error;
533
534 tlv = (struct bhnd_nvram_tlv *)nv;
535
536 /* Fetch pointer to the TLV_ENV record */
537 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
538 BHND_NV_PANIC("invalid cookiep: %p", cookiep);
539
540 /* Parse value pointer and length from key\0value data */
541 error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env),
542 '\0', NULL, NULL, &val, len);
543 if (error)
544 BHND_NV_PANIC("unexpected error parsing '%s'", env->envp);
545
546 /* Type is always CSTR */
547 *type = BHND_NVRAM_TYPE_STRING;
548
549 return (val);
550 }
551
552 static const char *
bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data * nv,void * cookiep)553 bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
554 {
555 struct bhnd_nvram_tlv *tlv;
556 const struct bhnd_nvram_tlv_env *env;
557
558 tlv = (struct bhnd_nvram_tlv *)nv;
559
560 /* Fetch pointer to the TLV_ENV record */
561 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
562 BHND_NV_PANIC("invalid cookiep: %p", cookiep);
563
564 /* Return name pointer */
565 return (&env->envp[0]);
566 }
567
568 static int
bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data * nv,const char * name,bhnd_nvram_val * value,bhnd_nvram_val ** result)569 bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
570 bhnd_nvram_val *value, bhnd_nvram_val **result)
571 {
572 bhnd_nvram_val *str;
573 const char *inp;
574 bhnd_nvram_type itype;
575 size_t ilen;
576 size_t name_len, tlv_nremain;
577 int error;
578
579 tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN;
580
581 /* Name (trimmed of any path prefix) must be valid */
582 if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
583 return (EINVAL);
584
585 /* 'name=' must fit within the maximum TLV_ENV record length */
586 name_len = strlen(name) + 1; /* '=' */
587 if (tlv_nremain < name_len) {
588 BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n",
589 name);
590 return (EINVAL);
591 }
592 tlv_nremain -= name_len;
593
594 /* Convert value to a (bcm-formatted) string */
595 error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
596 value, BHND_NVRAM_VAL_DYNAMIC);
597 if (error)
598 return (error);
599
600 /* The string value must fit within remaining TLV_ENV record length */
601 inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
602 if (tlv_nremain < ilen) {
603 BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n",
604 BHND_NV_PRINT_WIDTH(ilen), inp);
605
606 bhnd_nvram_val_release(str);
607 return (EINVAL);
608 }
609 tlv_nremain -= name_len;
610
611 /* Success. Transfer result ownership to the caller. */
612 *result = str;
613 return (0);
614 }
615
616 static int
bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data * nv,const char * name)617 bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
618 {
619 /* We permit deletion of any variable */
620 return (0);
621 }
622
623 /**
624 * Iterate over the records starting at @p next, returning the parsed
625 * record's @p tag, @p size, and @p offset.
626 *
627 * @param io The I/O context to parse.
628 * @param[in,out] next The next offset to be parsed, or 0x0
629 * to begin parsing. Upon successful
630 * return, will be set to the offset of the
631 * next record (or EOF, if
632 * NVRAM_TLV_TYPE_END was parsed).
633 * @param[out] offset The record's value offset.
634 * @param[out] tag The record's tag.
635 *
636 * @retval 0 success
637 * @retval EINVAL if parsing @p io as TLV fails.
638 * @retval non-zero if reading @p io otherwise fails, a regular unix error
639 * code will be returned.
640 */
641 static int
bhnd_nvram_tlv_next_record(struct bhnd_nvram_io * io,size_t * next,size_t * offset,uint8_t * tag)642 bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t
643 *offset, uint8_t *tag)
644 {
645 size_t io_offset, io_size;
646 uint16_t parsed_len;
647 uint8_t len_hdr[2];
648 int error;
649
650 io_offset = *next;
651 io_size = bhnd_nvram_io_getsize(io);
652
653 /* Save the record offset */
654 if (offset != NULL)
655 *offset = io_offset;
656
657 /* Fetch initial tag */
658 error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag));
659 if (error)
660 return (error);
661 io_offset++;
662
663 /* EOF */
664 if (*tag == NVRAM_TLV_TYPE_END) {
665 *next = io_offset;
666 return (0);
667 }
668
669 /* Read length field */
670 if (*tag & NVRAM_TLV_TF_U8_LEN) {
671 error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
672 sizeof(len_hdr[0]));
673 if (error) {
674 BHND_NV_LOG("error reading TLV record size: %d\n",
675 error);
676 return (error);
677 }
678
679 parsed_len = len_hdr[0];
680 io_offset++;
681 } else {
682 error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
683 sizeof(len_hdr));
684 if (error) {
685 BHND_NV_LOG("error reading 16-bit TLV record "
686 "size: %d\n", error);
687 return (error);
688 }
689
690 parsed_len = (len_hdr[0] << 8) | len_hdr[1];
691 io_offset += 2;
692 }
693
694 /* Advance to next record */
695 if (parsed_len > io_size || io_size - parsed_len < io_offset) {
696 /* Hit early EOF */
697 BHND_NV_LOG("TLV record length %hu truncated by input "
698 "size of %zu\n", parsed_len, io_size);
699 return (EINVAL);
700 }
701
702 *next = io_offset + parsed_len;
703
704 /* Valid record found */
705 return (0);
706 }
707
708 /**
709 * Parse the TLV data in @p io to determine the total size of the TLV
710 * data mapped by @p io (which may be less than the size of @p io).
711 */
712 static int
bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io * io,size_t * size)713 bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size)
714 {
715 size_t next;
716 uint8_t tag;
717 int error;
718
719 /* We have to perform a minimal parse to determine the actual length */
720 next = 0x0;
721 *size = 0x0;
722
723 /* Iterate over the input until we hit END tag or the read fails */
724 do {
725 error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag);
726 if (error)
727 return (error);
728 } while (tag != NVRAM_TLV_TYPE_END);
729
730 /* Offset should now point to EOF */
731 BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io),
732 ("parse returned invalid EOF offset"));
733
734 *size = next;
735 return (0);
736 }
737
738 /**
739 * Iterate over the records in @p tlv, returning a pointer to the next
740 * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached.
741 *
742 * @param tlv The TLV instance.
743 * @param[in,out] next The next offset to be parsed, or 0x0
744 * to begin parsing. Upon successful
745 * return, will be set to the offset of the
746 * next record.
747 */
748 static struct bhnd_nvram_tlv_env *
bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv * tlv,size_t * next,void ** cookiep)749 bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next,
750 void **cookiep)
751 {
752 uint8_t tag;
753 int error;
754
755 /* Find the next TLV_ENV record, starting at @p next */
756 do {
757 void *c;
758 size_t offset;
759
760 /* Fetch the next TLV record */
761 error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset,
762 &tag);
763 if (error) {
764 BHND_NV_LOG("unexpected error in next_record(): %d\n",
765 error);
766 return (NULL);
767 }
768
769 /* Only interested in ENV records */
770 if (tag != NVRAM_TLV_TYPE_ENV)
771 continue;
772
773 /* Map and return TLV_ENV record pointer */
774 c = bhnd_nvram_tlv_to_cookie(tlv, offset);
775
776 /* Provide the cookiep value for the returned record */
777 if (cookiep != NULL)
778 *cookiep = c;
779
780 return (bhnd_nvram_tlv_get_env(tlv, c));
781 } while (tag != NVRAM_TLV_TYPE_END);
782
783 /* No remaining ENV records */
784 return (NULL);
785 }
786
787 /**
788 * Return a pointer to the TLV_ENV record for @p cookiep, or NULL
789 * if none vailable.
790 */
791 static struct bhnd_nvram_tlv_env *
bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv * tlv,void * cookiep)792 bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep)
793 {
794 struct bhnd_nvram_tlv_env *env;
795 void *ptr;
796 size_t navail;
797 size_t io_offset, io_size;
798 int error;
799
800 io_size = bhnd_nvram_io_getsize(tlv->data);
801 io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep);
802
803 /* At EOF? */
804 if (io_offset == io_size)
805 return (NULL);
806
807 /* Fetch non-const pointer to the record entry */
808 error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr,
809 sizeof(env->hdr), &navail);
810 if (error) {
811 /* Should never occur with a valid cookiep */
812 BHND_NV_LOG("error mapping record for cookiep: %d\n", error);
813 return (NULL);
814 }
815
816 /* Validate the record pointer */
817 env = ptr;
818 if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) {
819 /* Should never occur with a valid cookiep */
820 BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep);
821 return (NULL);
822 }
823
824 /* Is the required variable name data is mapped? */
825 if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size ||
826 env->hdr.size == sizeof(env->flags))
827 {
828 /* Should never occur with a valid cookiep */
829 BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n",
830 cookiep);
831 return (NULL);
832 }
833
834 return (env);
835 }
836
837 /**
838 * Return a cookiep for the given I/O offset.
839 */
840 static void *
bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv * tlv,size_t io_offset)841 bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset)
842 {
843 const void *ptr;
844 int error;
845
846 BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data),
847 ("io_offset %zu out-of-range", io_offset));
848 BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
849 ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
850
851 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL);
852 if (error)
853 BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
854
855 ptr = (const uint8_t *)ptr + io_offset;
856 return (__DECONST(void *, ptr));
857 }
858
859 /* Convert a cookiep back to an I/O offset */
860 static size_t
bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv * tlv,void * cookiep)861 bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep)
862 {
863 const void *ptr;
864 intptr_t offset;
865 size_t io_size;
866 int error;
867
868 BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
869
870 io_size = bhnd_nvram_io_getsize(tlv->data);
871
872 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL);
873 if (error)
874 BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
875
876 offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
877 BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
878 BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
879 BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
880
881 return ((size_t)offset);
882 }
883