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
65struct bhnd_nvram_bcmraw;
66
67/** BCM-RAW NVRAM data class instance */
68struct 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
75BHND_NVRAM_DATA_CLASS_DEFN(bcmraw, "Broadcom (RAW)",
76    BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcmraw))
77
78static int
79bhnd_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
136static int
137bhnd_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
144static int
145bhnd_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 */
246static int
247bhnd_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
331static int
332bhnd_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
349static void
350bhnd_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
358static bhnd_nvram_plist *
359bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv)
360{
361	return (NULL);
362}
363
364static size_t
365bhnd_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
372static uint32_t
373bhnd_nvram_bcmraw_caps(struct bhnd_nvram_data *nv)
374{
375	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
376}
377
378static const char *
379bhnd_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
404static void *
405bhnd_nvram_bcmraw_find(struct bhnd_nvram_data *nv, const char *name)
406{
407	return (bhnd_nvram_data_generic_find(nv, name));
408}
409
410static int
411bhnd_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
423static int
424bhnd_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
430static int
431bhnd_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
437static const void *
438bhnd_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
452static const char *
453bhnd_nvram_bcmraw_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
454{
455	/* Cookie points to key\0value\0 */
456	return (cookiep);
457}
458
459static int
460bhnd_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
481static int
482bhnd_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