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

/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2008-2012 Juli Mallett <jmallett@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.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE.
 *
 * $FreeBSD$
 */

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

#include <sys/param.h>
#include <sys/bio.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>

#include <geom/geom.h>

#include <machine/cpuregs.h>

#include <dev/gxemul/disk/gxemul_diskreg.h>

struct gxemul_disk_softc {
	device_t sc_dev;
	uint64_t sc_size;
	struct g_geom *sc_geom;
	struct g_provider *sc_provider;
};

static struct mtx gxemul_disk_controller_mutex;

static g_start_t	gxemul_disk_start;
static g_access_t	gxemul_disk_access;

struct g_class g_gxemul_disk_class = {
	.name = "GXemul",
	.version = G_VERSION,
	.start = gxemul_disk_start,
	.access = gxemul_disk_access,
};

DECLARE_GEOM_CLASS(g_gxemul_disk_class, g_gxemul_disk);

static void	gxemul_disk_identify(driver_t *, device_t);
static int	gxemul_disk_probe(device_t);
static int	gxemul_disk_attach(device_t);
static void	gxemul_disk_attach_geom(void *, int);

static int	gxemul_disk_read(unsigned, void *, off_t);
static int	gxemul_disk_size(unsigned, uint64_t *);
static int	gxemul_disk_write(unsigned, const void *, off_t);

static void
gxemul_disk_start(struct bio *bp)
{
	struct gxemul_disk_softc *sc;
	unsigned diskid;
	off_t offset;
	uint8_t *buf;
	int error;

	sc = bp->bio_to->geom->softc;
	diskid = device_get_unit(sc->sc_dev);

	if ((bp->bio_length % GXEMUL_DISK_DEV_BLOCKSIZE) != 0) {
		g_io_deliver(bp, EINVAL);
		return;
	}

	buf = bp->bio_data;
	offset = bp->bio_offset;
	bp->bio_resid = bp->bio_length;
	while (bp->bio_resid != 0) {
		switch (bp->bio_cmd) {
		case BIO_READ:
			mtx_lock(&gxemul_disk_controller_mutex);
			error = gxemul_disk_read(diskid, buf, offset);
			mtx_unlock(&gxemul_disk_controller_mutex);
			break;
		case BIO_WRITE:
			mtx_lock(&gxemul_disk_controller_mutex);
			error = gxemul_disk_write(diskid, buf, offset);
			mtx_unlock(&gxemul_disk_controller_mutex);
			break;
		default:
			g_io_deliver(bp, EOPNOTSUPP);
			return;
		}
		if (error != 0) {
			g_io_deliver(bp, error);
			return;
		}

		buf += GXEMUL_DISK_DEV_BLOCKSIZE;
		offset += GXEMUL_DISK_DEV_BLOCKSIZE;
		bp->bio_completed += GXEMUL_DISK_DEV_BLOCKSIZE;
		bp->bio_resid -= GXEMUL_DISK_DEV_BLOCKSIZE;
	}

	g_io_deliver(bp, 0);
}

static int
gxemul_disk_access(struct g_provider *pp, int r, int w, int e)
{
	return (0);
}

static void
gxemul_disk_identify(driver_t *drv, device_t parent)
{
	unsigned diskid;

	mtx_init(&gxemul_disk_controller_mutex, "GXemul disk controller", NULL, MTX_DEF);

	mtx_lock(&gxemul_disk_controller_mutex);
	for (diskid = 0; diskid < 0x100; diskid++) {
		/*
		 * If we can read at offset 0, this disk id must be
		 * present enough.  If we get an error, stop looking.
		 * Disks in GXemul are allocated linearly from 0.
		 */
		if (gxemul_disk_read(diskid, NULL, 0) != 0)
			break;
		BUS_ADD_CHILD(parent, 0, "gxemul_disk", diskid);
	}
	mtx_unlock(&gxemul_disk_controller_mutex);
}

static int
gxemul_disk_probe(device_t dev)
{
	device_set_desc(dev, "GXemul test disk");

	return (BUS_PROBE_NOWILDCARD);
}

static void
gxemul_disk_attach_geom(void *arg, int flag)
{
	struct gxemul_disk_softc *sc;

	sc = arg;

	sc->sc_geom = g_new_geomf(&g_gxemul_disk_class, "%s", device_get_nameunit(sc->sc_dev));
	sc->sc_geom->softc = sc;

	sc->sc_provider = g_new_providerf(sc->sc_geom, "%s", sc->sc_geom->name);
	sc->sc_provider->sectorsize = GXEMUL_DISK_DEV_BLOCKSIZE;
	sc->sc_provider->mediasize = sc->sc_size;
	g_error_provider(sc->sc_provider, 0);
}

static int
gxemul_disk_attach(device_t dev)
{
	struct gxemul_disk_softc *sc;
	unsigned diskid;
	int error;

	diskid = device_get_unit(dev);

	sc = device_get_softc(dev);
	sc->sc_dev = dev;
	sc->sc_geom = NULL;
	sc->sc_provider = NULL;

	mtx_lock(&gxemul_disk_controller_mutex);
	error = gxemul_disk_size(diskid, &sc->sc_size);
	if (error != 0) {
		mtx_unlock(&gxemul_disk_controller_mutex);
		return (error);
	}
	mtx_unlock(&gxemul_disk_controller_mutex);

	g_post_event(gxemul_disk_attach_geom, sc, M_WAITOK, NULL);

	return (0);
}

static int
gxemul_disk_read(unsigned diskid, void *buf, off_t off)
{
	const volatile void *src;

	mtx_assert(&gxemul_disk_controller_mutex, MA_OWNED);

	if (off < 0 || off % GXEMUL_DISK_DEV_BLOCKSIZE != 0)
		return (EINVAL);

#ifdef _LP64
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET, (uint64_t)off);
#else
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_LO,
	    (uint32_t)(off & 0xffffffff));
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_HI,
	    (uint32_t)((off >> 32) & 0xffffffff));
#endif
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_DISKID, diskid);
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_START, GXEMUL_DISK_DEV_START_READ);
	switch (GXEMUL_DISK_DEV_READ(GXEMUL_DISK_DEV_STATUS)) {
	case GXEMUL_DISK_DEV_STATUS_FAILURE:
		return (EIO);
	default:
		break;
	}

	if (buf != NULL) {
		src = GXEMUL_DISK_DEV_FUNCTION(GXEMUL_DISK_DEV_BLOCK);
		memcpy(buf, (const void *)(uintptr_t)src,
		       GXEMUL_DISK_DEV_BLOCKSIZE);
	}

	return (0);
}

static int
gxemul_disk_size(unsigned diskid, uint64_t *sizep)
{
	uint64_t offset, ogood;
	uint64_t m, s;
	int error;

	m = 1;
	s = 3;
	ogood = 0;

	for (;;) {
		offset = (ogood * s) + (m * GXEMUL_DISK_DEV_BLOCKSIZE);

		error = gxemul_disk_read(diskid, NULL, offset);
		if (error != 0) {
			if (m == 1 && s == 1) {
				*sizep = ogood + GXEMUL_DISK_DEV_BLOCKSIZE;
				return (0);
			}
			if (m > 1)
				m /= 2;
			if (s > 1)
				s--;
			continue;
		}
		if (ogood == offset) {
			m = 1;
			continue;
		}
		ogood = offset;
		m++;
	}

	return (EDOOFUS);
}

static int
gxemul_disk_write(unsigned diskid, const void *buf, off_t off)
{
	volatile void *dst;

	mtx_assert(&gxemul_disk_controller_mutex, MA_OWNED);

	if (off < 0 || off % GXEMUL_DISK_DEV_BLOCKSIZE != 0)
		return (EINVAL);

#ifdef _LP64
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET, (uint64_t)off);
#else
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_LO,
	    (uint32_t)(off & 0xffffffff));
	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_HI,
	    (uint32_t)((off >> 32) & 0xffffffff));
#endif

	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_DISKID, diskid);

	dst = GXEMUL_DISK_DEV_FUNCTION(GXEMUL_DISK_DEV_BLOCK);
	memcpy((void *)(uintptr_t)dst, buf, GXEMUL_DISK_DEV_BLOCKSIZE);

	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_START, GXEMUL_DISK_DEV_START_WRITE);
	switch (GXEMUL_DISK_DEV_READ(GXEMUL_DISK_DEV_STATUS)) {
	case GXEMUL_DISK_DEV_STATUS_FAILURE:
		return (EIO);
	default:
		break;
	}

	return (0);
}

static device_method_t gxemul_disk_methods[] = {
	DEVMETHOD(device_probe,		gxemul_disk_probe),
	DEVMETHOD(device_identify,      gxemul_disk_identify),
	DEVMETHOD(device_attach,	gxemul_disk_attach),

	{ 0, 0 }
};

static driver_t gxemul_disk_driver = {
	"gxemul_disk", 
	gxemul_disk_methods, 
	sizeof (struct gxemul_disk_softc)
};

static devclass_t gxemul_disk_devclass;
DRIVER_MODULE(gxemul_disk, nexus, gxemul_disk_driver, gxemul_disk_devclass, 0, 0);