Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*-
 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/rman.h>

#include <machine/bus.h>

#include <dev/bhnd/bhnd.h>

#include "bhnd_nvram_private.h"

#include "bhnd_nvram_io.h"
#include "bhnd_nvram_iovar.h"

/**
 * BHND resource-backed NVRAM I/O context.
 */
struct bhnd_nvram_iores {
	struct bhnd_nvram_io	 io;		/**< common I/O instance state */
	struct bhnd_resource	*res;		/**< backing resource (borrowed ref) */
	size_t			 offset;	/**< offset within res */
	size_t			 size;		/**< size relative to the base offset */
	u_int			 bus_width;	/**< data type byte width to be used
						     when performing bus operations
						     on res. (1, 2, or 4 bytes) */
};

BHND_NVRAM_IOPS_DEFN(iores);

/**
 * Allocate and return a new I/O context backed by a borrowed reference to @p r.
 *
 * The caller is responsible for deallocating the returned I/O context via
 * bhnd_nvram_io_free().
 * 
 * @param	r		The resource to be mapped by the returned I/O
 *				context.
 * @param	offset		Offset 
 * @param	bus_width	The required I/O width (1, 2, or 4 bytes) to be
 *				used when reading from @p r.
 * 
 * @retval	bhnd_nvram_io	success.
 * @retval	NULL		if allocation fails, or an invalid argument
 *				is supplied.
 */
struct bhnd_nvram_io *
bhnd_nvram_iores_new(struct bhnd_resource *r, bus_size_t offset,
    bus_size_t size, u_int bus_width)
{
	struct bhnd_nvram_iores	*iores;
	rman_res_t		 r_start, r_size;

	/* Verify the bus width */
	switch (bus_width) {
	case 1:
	case 2:
	case 4:
		/* valid */
		break;
	default:
		BHND_NV_LOG("invalid bus width %u\n", bus_width);
		return (NULL);
	}

	/* offset/size must not exceed our internal size_t representation,
	 * or our bus_size_t usage (note that BUS_SPACE_MAXSIZE may be less
	 * than 2^(sizeof(bus_size_t) * 32). */
	if (size > SIZE_MAX || offset > SIZE_MAX) {
		BHND_NV_LOG("offset %#jx+%#jx exceeds SIZE_MAX\n",
		    (uintmax_t)offset, (uintmax_t)offset);
		return (NULL);
	}

	if (size > BUS_SPACE_MAXSIZE || offset > BUS_SPACE_MAXSIZE)
	{
		BHND_NV_LOG("offset %#jx+%#jx exceeds BUS_SPACE_MAXSIZE\n",
		    (uintmax_t)offset, (uintmax_t)offset);
		return (NULL);
	}

	/* offset/size fall within the resource's mapped range */
	r_size = rman_get_size(r->res);
	r_start = rman_get_start(r->res);
	if (r_size < offset || r_size < size || r_size - size < offset)
		return (NULL);

	/* offset/size must be bus_width aligned  */
	if ((r_start + offset) % bus_width != 0) {
		BHND_NV_LOG("base address %#jx+%#jx not aligned to bus width "
		    "%u\n", (uintmax_t)r_start, (uintmax_t)offset, bus_width);
		return (NULL);
	}

	if (size % bus_width != 0) {
		BHND_NV_LOG("size %#jx not aligned to bus width %u\n",
		    (uintmax_t)size, bus_width);
		return (NULL);
	}

	/* Allocate and return the I/O context */
	iores = malloc(sizeof(*iores), M_BHND_NVRAM, M_WAITOK);
	iores->io.iops = &bhnd_nvram_iores_ops;
	iores->res = r;
	iores->offset = offset;
	iores->size = size;
	iores->bus_width = bus_width;

	return (&iores->io);
}

static void
bhnd_nvram_iores_free(struct bhnd_nvram_io *io)
{
	free(io, M_BHND_NVRAM);
}

static size_t
bhnd_nvram_iores_getsize(struct bhnd_nvram_io *io)
{
	struct bhnd_nvram_iores	*iores = (struct bhnd_nvram_iores *)io;
	return (iores->size);
}

static int
bhnd_nvram_iores_setsize(struct bhnd_nvram_io *io, size_t size)
{
	/* unsupported */
	return (ENODEV);
}

static int
bhnd_nvram_iores_read_ptr(struct bhnd_nvram_io *io, size_t offset,
    const void **ptr, size_t nbytes, size_t *navail)
{
	/* unsupported */
	return (ENODEV);
}

static int
bhnd_nvram_iores_write_ptr(struct bhnd_nvram_io *io, size_t offset,
    void **ptr, size_t nbytes, size_t *navail)
{
	/* unsupported */
	return (ENODEV);
}

/**
 * Validate @p offset and @p nbytes:
 * 
 * - Verify that @p offset is mapped by the backing resource.
 * - If less than @p nbytes are available at @p offset, write the actual number
 *   of bytes available to @p nbytes.
 * - Verify that @p offset + @p nbytes are correctly aligned.
 */
static int
bhnd_nvram_iores_validate_req(struct bhnd_nvram_iores *iores, size_t offset,
    size_t *nbytes)
{
	/* Verify offset falls within the resource range */
	if (offset > iores->size)
		return (ENXIO);

	/* Check for eof */
	if (offset == iores->size) {
		*nbytes = 0;
		return (0);
	}

	/* Verify offset alignment */
	if (offset % iores->bus_width != 0)
		return (EFAULT);

	/* Limit nbytes to available range and verify size alignment */
	*nbytes = ummin(*nbytes, iores->size - offset);
	if (*nbytes < iores->bus_width && *nbytes % iores->bus_width != 0)
		return (EFAULT);

	return (0);
}

static int
bhnd_nvram_iores_read(struct bhnd_nvram_io *io, size_t offset, void *buffer,
    size_t nbytes)
{
	struct bhnd_nvram_iores	*iores;
	bus_size_t		 r_offset;
	size_t			 navail;
	int			 error;

	iores = (struct bhnd_nvram_iores *)io;

	/* Validate the request and determine the actual number of readable
	 * bytes */
	navail = nbytes;
	if ((error = bhnd_nvram_iores_validate_req(iores, offset, &navail)))
		return (error);

	/* At least nbytes must be readable */
	if (navail < nbytes)
		return (ENXIO);

	/* Handle zero length read */
	if (nbytes == 0)
		return (0);

	/* Determine actual resource offset and perform the read */
	r_offset = iores->offset + offset;
	switch (iores->bus_width) {
	case 1:
		bhnd_bus_read_region_stream_1(iores->res, r_offset, buffer,
		    nbytes);
		break;
	case 2:
		bhnd_bus_read_region_stream_2(iores->res, r_offset, buffer,
		    nbytes / 2);
		break;
	case 4:
		bhnd_bus_read_region_stream_4(iores->res, r_offset, buffer,
		    nbytes / 4);
		break;
	default:
		panic("unreachable!");
	}

	return (0);
}

static int
bhnd_nvram_iores_write(struct bhnd_nvram_io *io, size_t offset,
    void *buffer, size_t nbytes)
{
	struct bhnd_nvram_iores	*iores;
	size_t			 navail;
	bus_size_t		 r_offset;
	int			 error;

	iores = (struct bhnd_nvram_iores *)io;

	/* Validate the request and determine the actual number of writable
	 * bytes */
	navail = nbytes;
	if ((error = bhnd_nvram_iores_validate_req(iores, offset, &navail)))
		return (error);

	/* At least nbytes must be writable */
	if (navail < nbytes)
		return (ENXIO);

	/* Determine actual resource offset and perform the write */
	r_offset = iores->offset + offset;
	switch (iores->bus_width) {
	case 1:
		bhnd_bus_write_region_stream_1(iores->res, r_offset, buffer,
		    nbytes);
		break;
	case 2:
		bhnd_bus_write_region_stream_2(iores->res, r_offset, buffer,
		    nbytes / 2);
		break;
	case 4:
		bhnd_bus_write_region_stream_4(iores->res, r_offset, buffer,
		    nbytes / 4);
		break;
	default:
		panic("unreachable!");
	}

	return (0);
}