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/malloc.h>
34#include <sys/systm.h>
35#else /* !_KERNEL */
36#include <errno.h>
37#include <stdint.h>
38#include <stdlib.h>
39#include <string.h>
40#endif /* _KERNEL */
41
42#include "bhnd_nvram_private.h"
43
44#include "bhnd_nvram_io.h"
45#include "bhnd_nvram_iovar.h"
46
47/**
48 * Buffer-backed NVRAM I/O context.
49 *
50 * iobuf instances are gauranteed to provide persistent references to its
51 * backing contigious buffer via bhnd_nvram_io_read_ptr() and
52 * bhnd_nvram_io_write_ptr().
53 */
54struct bhnd_nvram_iobuf {
55	struct bhnd_nvram_io	 io;		/**< common I/O instance state */
56	void			*buf;		/**< backing buffer. if inline-allocated, will
57						     be a reference to data[]. */
58	size_t			 size;		/**< size of @p buf */
59	size_t			 capacity;	/**< capacity of @p buf */
60	uint8_t			 data[];	/**< inline buffer allocation */
61};
62
63BHND_NVRAM_IOPS_DEFN(iobuf)
64
65/**
66 * Allocate and return a new I/O context with an uninitialized
67 * buffer of @p size and @p capacity.
68 *
69 * The caller is responsible for deallocating the returned I/O context via
70 * bhnd_nvram_io_free().
71 *
72 * If @p capacity is less than @p size, a capacity of @p size will be used.
73 *
74 * @param	size		The initial size of the I/O context.
75 * @param	capacity	The total capacity of the I/O context buffer;
76 *				the returned I/O context may be resized up to
77 *				@p capacity via bhnd_nvram_io_setsize().
78 *
79 * @retval	bhnd_nvram_iobuf	success.
80 * @retval	NULL			allocation failed.
81 * @retval	NULL			the requested @p capacity is less than
82 *					@p size.
83 */
84struct bhnd_nvram_io *
85bhnd_nvram_iobuf_empty(size_t size, size_t capacity)
86{
87	struct bhnd_nvram_iobuf	*iobuf;
88	size_t			 iosz;
89	bool			 inline_alloc;
90
91	/* Sanity check the capacity */
92	if (size > capacity)
93		return (NULL);
94
95	/* Would sizeof(iobuf)+capacity overflow? */
96	if (SIZE_MAX - sizeof(*iobuf) < capacity) {
97		inline_alloc = false;
98		iosz = sizeof(*iobuf);
99	} else {
100		inline_alloc = true;
101		iosz = sizeof(*iobuf) + capacity;
102	}
103
104	/* Allocate I/O context */
105	iobuf = bhnd_nv_malloc(iosz);
106	if (iobuf == NULL)
107		return (NULL);
108
109	iobuf->io.iops = &bhnd_nvram_iobuf_ops;
110	iobuf->buf = NULL;
111	iobuf->size = size;
112	iobuf->capacity = capacity;
113
114	/* Either allocate our backing buffer, or initialize the
115	 * backing buffer with a reference to our inline allocation. */
116	if (inline_alloc)
117		iobuf->buf = &iobuf->data;
118	else
119		iobuf->buf = bhnd_nv_malloc(iobuf->capacity);
120
121	if (iobuf->buf == NULL) {
122		bhnd_nv_free(iobuf);
123		return (NULL);
124	}
125
126	return (&iobuf->io);
127}
128
129/**
130 * Allocate and return a new I/O context, copying @p size from @p buffer.
131 *
132 * The caller is responsible for deallocating the returned I/O context via
133 * bhnd_nvram_io_free().
134 *
135 * @param	buffer	The buffer data be copied by the returned I/O context.
136 * @param	size	The size of @p buffer, in bytes.
137 *
138 * @retval	bhnd_nvram_io	success.
139 * @retval	NULL		allocation failed.
140 */
141struct bhnd_nvram_io *
142bhnd_nvram_iobuf_new(const void *buffer, size_t size)
143{
144	struct bhnd_nvram_io	*io;
145	struct bhnd_nvram_iobuf	*iobuf;
146
147	/* Allocate the iobuf */
148	if ((io = bhnd_nvram_iobuf_empty(size, size)) == NULL)
149		return (NULL);
150
151	/* Copy the input to our new iobuf instance */
152	iobuf = (struct bhnd_nvram_iobuf *)io;
153	memcpy(iobuf->buf, buffer, iobuf->size);
154
155	return (io);
156}
157
158/**
159 * Allocate and return a new I/O context providing an in-memory copy
160 * of the data mapped by @p src.
161 *
162 * The caller is responsible for deallocating the returned I/O context via
163 * bhnd_nvram_io_free().
164 *
165 * @param	src	The I/O context to be copied.
166 *
167 * @retval	bhnd_nvram_io	success.
168 * @retval	NULL		allocation failed.
169 * @retval	NULL		copying @p src failed.
170 */
171struct bhnd_nvram_io *
172bhnd_nvram_iobuf_copy(struct bhnd_nvram_io *src)
173{
174	return (bhnd_nvram_iobuf_copy_range(src, 0x0,
175	    bhnd_nvram_io_getsize(src)));
176}
177
178/**
179 * Allocate and return a new I/O context providing an in-memory copy
180 * of @p size bytes mapped at @p offset by @p src.
181 *
182 * The caller is responsible for deallocating the returned I/O context via
183 * bhnd_nvram_io_free().
184 *
185 * @param	src	The I/O context to be copied.
186 * @param	offset	The offset of the bytes to be copied from @p src.
187 * @param	size	The number of bytes to copy at @p offset from @p src.
188 *
189 * @retval	bhnd_nvram_io	success.
190 * @retval	NULL		allocation failed.
191 * @retval	NULL		copying @p src failed.
192 */
193struct bhnd_nvram_io *
194bhnd_nvram_iobuf_copy_range(struct bhnd_nvram_io *src, size_t offset,
195    size_t size)
196{
197	struct bhnd_nvram_io	*io;
198	struct bhnd_nvram_iobuf	*iobuf;
199	int			 error;
200
201	/* Check if offset+size would overflow */
202	if (SIZE_MAX - size < offset)
203		return (NULL);
204
205	/* Allocate the iobuf instance */
206	if ((io = bhnd_nvram_iobuf_empty(size, size)) == NULL)
207		return (NULL);
208
209	/* Copy the input I/O context */
210	iobuf = (struct bhnd_nvram_iobuf *)io;
211	if ((error = bhnd_nvram_io_read(src, offset, iobuf->buf, size))) {
212		bhnd_nvram_io_free(&iobuf->io);
213		return (NULL);
214	}
215
216	return (io);
217}
218
219static void
220bhnd_nvram_iobuf_free(struct bhnd_nvram_io *io)
221{
222	struct bhnd_nvram_iobuf	*iobuf = (struct bhnd_nvram_iobuf *)io;
223
224	/* Free the backing buffer if it wasn't allocated inline */
225	if (iobuf->buf != &iobuf->data)
226		bhnd_nv_free(iobuf->buf);
227
228	bhnd_nv_free(iobuf);
229}
230
231static size_t
232bhnd_nvram_iobuf_getsize(struct bhnd_nvram_io *io)
233{
234	struct bhnd_nvram_iobuf	*iobuf = (struct bhnd_nvram_iobuf *)io;
235	return (iobuf->size);
236}
237
238static int
239bhnd_nvram_iobuf_setsize(struct bhnd_nvram_io *io, size_t size)
240{
241	struct bhnd_nvram_iobuf	*iobuf = (struct bhnd_nvram_iobuf *)io;
242
243	/* Can't exceed the actual capacity */
244	if (size > iobuf->capacity)
245		return (ENXIO);
246
247	iobuf->size = size;
248	return (0);
249}
250
251/* Common iobuf_(read|write)_ptr implementation */
252static int
253bhnd_nvram_iobuf_ptr(struct bhnd_nvram_iobuf *iobuf, size_t offset, void **ptr,
254    size_t nbytes, size_t *navail)
255{
256	size_t avail;
257
258	/* Verify offset+nbytes fall within the buffer range */
259	if (offset > iobuf->size)
260		return (ENXIO);
261
262	avail = iobuf->size - offset;
263	if (avail < nbytes)
264		return (ENXIO);
265
266	/* Valid I/O range, provide a pointer to the buffer and the
267	 * total count of available bytes */
268	*ptr = ((uint8_t *)iobuf->buf) + offset;
269	if (navail != NULL)
270		*navail = avail;
271
272	return (0);
273}
274
275static int
276bhnd_nvram_iobuf_read_ptr(struct bhnd_nvram_io *io, size_t offset,
277    const void **ptr, size_t nbytes, size_t *navail)
278{
279	struct bhnd_nvram_iobuf	*iobuf;
280	void			*ioptr;
281	int			 error;
282
283	iobuf = (struct bhnd_nvram_iobuf *) io;
284
285	/* Return a pointer into our backing buffer */
286	error = bhnd_nvram_iobuf_ptr(iobuf, offset, &ioptr, nbytes, navail);
287	if (error)
288		return (error);
289
290	*ptr = ioptr;
291
292	return (0);
293}
294
295static int
296bhnd_nvram_iobuf_write_ptr(struct bhnd_nvram_io *io, size_t offset,
297    void **ptr, size_t nbytes, size_t *navail)
298{
299	struct bhnd_nvram_iobuf	*iobuf;
300
301	iobuf = (struct bhnd_nvram_iobuf *) io;
302
303	/* Return a pointer into our backing buffer */
304	return (bhnd_nvram_iobuf_ptr(iobuf, offset, ptr, nbytes, navail));
305}
306
307static int
308bhnd_nvram_iobuf_read(struct bhnd_nvram_io *io, size_t offset, void *buffer,
309    size_t nbytes)
310{
311	const void	*ptr;
312	int		 error;
313
314	/* Try to fetch a direct pointer for at least nbytes */
315	if ((error = bhnd_nvram_io_read_ptr(io, offset, &ptr, nbytes, NULL)))
316		return (error);
317
318	/* Copy out the requested data */
319	memcpy(buffer, ptr, nbytes);
320	return (0);
321}
322
323static int
324bhnd_nvram_iobuf_write(struct bhnd_nvram_io *io, size_t offset,
325    void *buffer, size_t nbytes)
326{
327	void	*ptr;
328	int	 error;
329
330	/* Try to fetch a direct pointer for at least nbytes */
331	if ((error = bhnd_nvram_io_write_ptr(io, offset, &ptr, nbytes, NULL)))
332		return (error);
333
334	/* Copy in the provided data */
335	memcpy(ptr, buffer, nbytes);
336	return (0);
337}
338