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