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
63struct bhnd_nvram_bcmraw;
64
65/** BCM-RAW NVRAM data class instance */
66struct 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
73BHND_NVRAM_DATA_CLASS_DEFN(bcmraw, "Broadcom (RAW)",
74    BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcmraw))
75
76static int
77bhnd_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
134static int
135bhnd_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
142static int
143bhnd_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 */
244static int
245bhnd_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
329static int
330bhnd_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
347static void
348bhnd_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
356static bhnd_nvram_plist *
357bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv)
358{
359	return (NULL);
360}
361
362static size_t
363bhnd_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
370static uint32_t
371bhnd_nvram_bcmraw_caps(struct bhnd_nvram_data *nv)
372{
373	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
374}
375
376static const char *
377bhnd_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
402static void *
403bhnd_nvram_bcmraw_find(struct bhnd_nvram_data *nv, const char *name)
404{
405	return (bhnd_nvram_data_generic_find(nv, name));
406}
407
408static int
409bhnd_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
421static int
422bhnd_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
428static int
429bhnd_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
435static const void *
436bhnd_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
450static const char *
451bhnd_nvram_bcmraw_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
452{
453	/* Cookie points to key\0value\0 */
454	return (cookiep);
455}
456
457static int
458bhnd_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
479static int
480bhnd_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