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 __FBSDID("$FreeBSD$");
32 
33 #ifdef _KERNEL
34 
35 #include <sys/param.h>
36 #include <sys/ctype.h>
37 #include <sys/malloc.h>
38 #include <sys/systm.h>
39 
40 #else /* !_KERNEL */
41 
42 #include <ctype.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 
48 #endif /* _KERNEL */
49 
50 #include "bhnd_nvram_private.h"
51 
52 #include "bhnd_nvram_datavar.h"
53 #include "bhnd_nvram_data_bcmvar.h"
54 
55 /*
56  * Broadcom-RAW NVRAM data class.
57  *
58  * The Broadcom NVRAM NUL-delimited ASCII format is used by most
59  * Broadcom SoCs.
60  *
61  * The NVRAM data is encoded as a stream of of NUL-terminated 'key=value'
62  * strings; the end of the stream is denoted by a single extra NUL character.
63  */
64 
65 struct bhnd_nvram_bcmraw;
66 
67 /** BCM-RAW NVRAM data class instance */
68 struct bhnd_nvram_bcmraw {
69 	struct bhnd_nvram_data		 nv;	/**< common instance state */
70 	char				*data;	/**< backing buffer */
71 	size_t				 size;	/**< buffer size */
72 	size_t				 count;	/**< variable count */
73 };
74 
75 BHND_NVRAM_DATA_CLASS_DEFN(bcmraw, "Broadcom (RAW)",
76     BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcmraw))
77 
78 static int
79 bhnd_nvram_bcmraw_probe(struct bhnd_nvram_io *io)
80 {
81 	char	 envp[16];
82 	size_t	 envp_len;
83 	size_t	 io_size;
84 	int	 error;
85 
86 	io_size = bhnd_nvram_io_getsize(io);
87 
88 	/*
89 	 * Fetch initial bytes
90 	 */
91 	envp_len = bhnd_nv_ummin(sizeof(envp), io_size);
92 	if ((error = bhnd_nvram_io_read(io, 0x0, envp, envp_len)))
93 		return (error);
94 
95 	/* An empty BCM-RAW buffer should still contain a single terminating
96 	 * NUL */
97 	if (envp_len == 0)
98 		return (ENXIO);
99 
100 	if (envp_len == 1) {
101 		if (envp[0] != '\0')
102 			return (ENXIO);
103 
104 		return (BHND_NVRAM_DATA_PROBE_MAYBE);
105 	}
106 
107 	/* Must contain only printable ASCII characters delimited
108 	 * by NUL record delimiters */
109 	for (size_t i = 0; i < envp_len; i++) {
110 		char c = envp[i];
111 
112 		/* If we hit a newline, this is probably BCM-TXT */
113 		if (c == '\n')
114 			return (ENXIO);
115 
116 		if (c == '\0' && !bhnd_nv_isprint(c))
117 			continue;
118 	}
119 
120 	/* A valid BCM-RAW buffer should contain a terminating NUL for
121 	 * the last record, followed by a final empty record terminated by
122 	 * NUL */
123 	envp_len = 2;
124 	if (io_size < envp_len)
125 		return (ENXIO);
126 
127 	if ((error = bhnd_nvram_io_read(io, io_size-envp_len, envp, envp_len)))
128 		return (error);
129 
130 	if (envp[0] != '\0' || envp[1] != '\0')
131 		return (ENXIO);
132 
133 	return (BHND_NVRAM_DATA_PROBE_MAYBE + 1);
134 }
135 
136 static int
137 bhnd_nvram_bcmraw_getvar_direct(struct bhnd_nvram_io *io, const char *name,
138     void *buf, size_t *len, bhnd_nvram_type type)
139 {
140 	return (bhnd_nvram_bcm_getvar_direct_common(io, name, buf, len, type,
141 	    false));
142 }
143 
144 static int
145 bhnd_nvram_bcmraw_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
146     bhnd_nvram_plist *options, void *outp, size_t *olen)
147 {
148 	bhnd_nvram_prop	*prop;
149 	size_t		 limit, nbytes;
150 	int		 error;
151 
152 	/* Determine output byte limit */
153 	if (outp != NULL)
154 		limit = *olen;
155 	else
156 		limit = 0;
157 
158 	nbytes = 0;
159 
160 	/* Write all properties */
161 	prop = NULL;
162 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
163 		const char	*name;
164 		char		*p;
165 		size_t		 prop_limit;
166 		size_t		 name_len, value_len;
167 
168 		if (outp == NULL || limit < nbytes) {
169 			p = NULL;
170 			prop_limit = 0;
171 		} else {
172 			p = ((char *)outp) + nbytes;
173 			prop_limit = limit - nbytes;
174 		}
175 
176 		/* Fetch and write name + '=' to output */
177 		name = bhnd_nvram_prop_name(prop);
178 		name_len = strlen(name) + 1;
179 
180 		if (prop_limit > name_len) {
181 			memcpy(p, name, name_len - 1);
182 			p[name_len - 1] = '=';
183 
184 			prop_limit -= name_len;
185 			p += name_len;
186 		} else {
187 			prop_limit = 0;
188 			p = NULL;
189 		}
190 
191 		/* Advance byte count */
192 		if (SIZE_MAX - nbytes < name_len)
193 			return (EFTYPE); /* would overflow size_t */
194 
195 		nbytes += name_len;
196 
197 		/* Attempt to write NUL-terminated value to output */
198 		value_len = prop_limit;
199 		error = bhnd_nvram_prop_encode(prop, p, &value_len,
200 		    BHND_NVRAM_TYPE_STRING);
201 
202 		/* If encoding failed for any reason other than ENOMEM (which
203 		 * we'll detect and report after encoding all properties),
204 		 * return immediately */
205 		if (error && error != ENOMEM) {
206 			BHND_NV_LOG("error serializing %s to required type "
207 			    "%s: %d\n", name,
208 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
209 			    error);
210 			return (error);
211 		}
212 
213 		/* Advance byte count */
214 		if (SIZE_MAX - nbytes < value_len)
215 			return (EFTYPE); /* would overflow size_t */
216 
217 		nbytes += value_len;
218 	}
219 
220 	/* Write terminating '\0' */
221 	if (limit > nbytes)
222 		*((char *)outp + nbytes) = '\0';
223 
224 	if (nbytes == SIZE_MAX)
225 		return (EFTYPE); /* would overflow size_t */
226 	else
227 		nbytes++;
228 
229 	/* Provide required length */
230 	*olen = nbytes;
231 	if (limit < *olen) {
232 		if (outp == NULL)
233 			return (0);
234 
235 		return (ENOMEM);
236 	}
237 
238 	return (0);
239 }
240 
241 /**
242  * Initialize @p bcm with the provided NVRAM data mapped by @p src.
243  *
244  * @param bcm A newly allocated data instance.
245  */
246 static int
247 bhnd_nvram_bcmraw_init(struct bhnd_nvram_bcmraw *bcm, struct bhnd_nvram_io *src)
248 {
249 	size_t	 io_size;
250 	size_t	 capacity, offset;
251 	int	 error;
252 
253 	/* Fetch the input image size */
254 	io_size = bhnd_nvram_io_getsize(src);
255 
256 	/* Allocate a buffer large enough to hold the NVRAM image, and
257 	 * an extra EOF-signaling NUL (on the chance it's missing from the
258 	 * source data) */
259 	if (io_size == SIZE_MAX)
260 		return (ENOMEM);
261 
262 	capacity = io_size + 1 /* room for extra NUL */;
263 	bcm->size = io_size;
264 	if ((bcm->data = bhnd_nv_malloc(capacity)) == NULL)
265 		return (ENOMEM);
266 
267 	/* Copy in the NVRAM image */
268 	if ((error = bhnd_nvram_io_read(src, 0x0, bcm->data, io_size)))
269 		return (error);
270 
271 	/* Process the buffer */
272 	bcm->count = 0;
273 	for (offset = 0; offset < bcm->size; offset++) {
274 		char		*envp;
275 		const char	*name, *value;
276 		size_t		 envp_len;
277 		size_t		 name_len, value_len;
278 
279 		/* Parse the key=value string */
280 		envp = (char *) (bcm->data + offset);
281 		envp_len = strnlen(envp, bcm->size - offset);
282 		error = bhnd_nvram_parse_env(envp, envp_len, '=', &name,
283 					     &name_len, &value, &value_len);
284 		if (error) {
285 			BHND_NV_LOG("error parsing envp at offset %#zx: %d\n",
286 			    offset, error);
287 			return (error);
288 		}
289 
290 		/* Insert a '\0' character, replacing the '=' delimiter and
291 		 * allowing us to vend references directly to the variable
292 		 * name */
293 		*(envp + name_len) = '\0';
294 
295 		/* Add to variable count */
296 		bcm->count++;
297 
298 		/* Seek past the value's terminating '\0' */
299 		offset += envp_len;
300 		if (offset == io_size) {
301 			BHND_NV_LOG("missing terminating NUL at offset %#zx\n",
302 			    offset);
303 			return (EINVAL);
304 		}
305 
306 		/* If we hit EOF without finding a terminating NUL
307 		 * byte, we need to append it */
308 		if (++offset == bcm->size) {
309 			BHND_NV_ASSERT(offset < capacity,
310 			    ("appending past end of buffer"));
311 			bcm->size++;
312 			*(bcm->data + offset) = '\0';
313 		}
314 
315 		/* Check for explicit EOF (encoded as a single empty NUL
316 		 * terminated string) */
317 		if (*(bcm->data + offset) == '\0')
318 			break;
319 	}
320 
321 	/* Reclaim any unused space in he backing buffer */
322 	if (offset < bcm->size) {
323 		bcm->data = bhnd_nv_reallocf(bcm->data, bcm->size);
324 		if (bcm->data == NULL)
325 			return (ENOMEM);
326 	}
327 
328 	return (0);
329 }
330 
331 static int
332 bhnd_nvram_bcmraw_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
333 {
334 	struct bhnd_nvram_bcmraw	*bcm;
335 	int				 error;
336 
337 	bcm = (struct bhnd_nvram_bcmraw *)nv;
338 
339 	/* Parse the BCM input data and initialize our backing
340 	 * data representation */
341 	if ((error = bhnd_nvram_bcmraw_init(bcm, io))) {
342 		bhnd_nvram_bcmraw_free(nv);
343 		return (error);
344 	}
345 
346 	return (0);
347 }
348 
349 static void
350 bhnd_nvram_bcmraw_free(struct bhnd_nvram_data *nv)
351 {
352 	struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv;
353 
354 	if (bcm->data != NULL)
355 		bhnd_nv_free(bcm->data);
356 }
357 
358 static bhnd_nvram_plist *
359 bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv)
360 {
361 	return (NULL);
362 }
363 
364 static size_t
365 bhnd_nvram_bcmraw_count(struct bhnd_nvram_data *nv)
366 {
367 	struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv;
368 
369 	return (bcm->count);
370 }
371 
372 static uint32_t
373 bhnd_nvram_bcmraw_caps(struct bhnd_nvram_data *nv)
374 {
375 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
376 }
377 
378 static const char *
379 bhnd_nvram_bcmraw_next(struct bhnd_nvram_data *nv, void **cookiep)
380 {
381 	struct bhnd_nvram_bcmraw	*bcm;
382 	const char			*envp;
383 
384 	bcm = (struct bhnd_nvram_bcmraw *)nv;
385 
386 	if (*cookiep == NULL) {
387 		/* Start at the first NVRAM data record */
388 		envp = bcm->data;
389 	} else {
390 		/* Seek to next record */
391 		envp = *cookiep;
392 		envp += strlen(envp) + 1;	/* key + '\0' */
393 		envp += strlen(envp) + 1;	/* value + '\0' */
394 	}
395 
396 	/* EOF? */
397 	if (*envp == '\0')
398 		return (NULL);
399 
400 	*cookiep = (void *)(uintptr_t)envp;
401 	return (envp);
402 }
403 
404 static void *
405 bhnd_nvram_bcmraw_find(struct bhnd_nvram_data *nv, const char *name)
406 {
407 	return (bhnd_nvram_data_generic_find(nv, name));
408 }
409 
410 static int
411 bhnd_nvram_bcmraw_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
412     void *cookiep2)
413 {
414 	if (cookiep1 < cookiep2)
415 		return (-1);
416 
417 	if (cookiep1 > cookiep2)
418 		return (1);
419 
420 	return (0);
421 }
422 
423 static int
424 bhnd_nvram_bcmraw_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
425     size_t *len, bhnd_nvram_type type)
426 {
427 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
428 }
429 
430 static int
431 bhnd_nvram_bcmraw_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
432     bhnd_nvram_val **value)
433 {
434 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
435 }
436 
437 static const void *
438 bhnd_nvram_bcmraw_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
439     size_t *len, bhnd_nvram_type *type)
440 {
441 	const char *envp;
442 
443 	/* Cookie points to key\0value\0 -- get the value address */
444 	envp = cookiep;
445 	envp += strlen(envp) + 1;	/* key + '\0' */
446 	*len = strlen(envp) + 1;	/* value + '\0' */
447 	*type = BHND_NVRAM_TYPE_STRING;
448 
449 	return (envp);
450 }
451 
452 static const char *
453 bhnd_nvram_bcmraw_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
454 {
455 	/* Cookie points to key\0value\0 */
456 	return (cookiep);
457 }
458 
459 static int
460 bhnd_nvram_bcmraw_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
461     bhnd_nvram_val *value, bhnd_nvram_val **result)
462 {
463 	bhnd_nvram_val	*str;
464 	int		 error;
465 
466 	/* Name (trimmed of any path prefix) must be valid */
467 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
468 		return (EINVAL);
469 
470 	/* Value must be bcm-formatted string */
471 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
472 	    value, BHND_NVRAM_VAL_DYNAMIC);
473 	if (error)
474 		return (error);
475 
476 	/* Success. Transfer result ownership to the caller. */
477 	*result = str;
478 	return (0);
479 }
480 
481 static int
482 bhnd_nvram_bcmraw_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
483 {
484 	/* We permit deletion of any variable */
485 	return (0);
486 }
487