/* $NetBSD: $ */

/*-
  * Copyright (c) 2014 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
  */

/* driver for soclib's block device */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.223 2008/07/02 17:28:56 ad Exp $");

#include "rnd.h"

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/disk.h>
#include <sys/dkio.h>
#include <sys/disklabel.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/kthread.h>
#include <sys/condvar.h>
#if NRND > 0
#include <sys/rnd.h>
#endif

#include <dev/ldvar.h>

#include <uvm/uvm.h>

#include <machine/autoconf.h>
#include <machine/bus.h>

#undef MD_DEBUG
#ifdef MD_DEBUG
#define DPRINTF(x) printf x;
#else
#define DPRINTF(x)
#endif

static int vcimd_match(device_t, cfdata_t, void *);
static void vcimd_attach(device_t, device_t, void *);
static int vcimd_print(void *, const char *);


struct vcimd_softc {
	device_t sc_dev;
	uint64_t sc_sectors;
	paddr_t sc_mdbase;
	uint64_t sc_mdsize;
	u_long sc_bcount; /* real byte count of xfer */
};

CFATTACH_DECL_NEW(vcimd, sizeof(struct vcimd_softc),
    vcimd_match, vcimd_attach, NULL, NULL);

extern struct cfdriver vcimd_cd;

int
vcimd_match(device_t parent, cfdata_t match, void *aux)
{
	struct tsardevs_attach_args *tsd = aux;
	if (strcmp(tsd->tsd_devtype, "soclib:memorydisk") != 0)
		return 0;
	return 1;
}

void
vcimd_attach(device_t parent, device_t self, void *aux)
{
	struct vcimd_softc *sc = device_private(self);
	struct tsardevs_attach_args *tsd = aux;

	aprint_normal(": VCI memory disk\n");

	sc->sc_dev = self;
	sc->sc_mdbase = tsd->tsd_reg[0].reg_addr;
	sc->sc_mdsize = tsd->tsd_reg[0].reg_size;
	sc->sc_sectors = tsd->tsd_reg[0].reg_size / DEV_BSIZE;

#if 0
	config_found_sm_loc(self, "vcimd", locs, NULL, vcimd_print,
	    config_stdsubmatch);
#endif
	config_found_ia(self, "vcimd", NULL, vcimd_print);
}

/*
 * Print autoconfiguration message for a sub-device.
 */
static int
vcimd_print(void *aux, const char *pnp)
{
	if (pnp != NULL)
		aprint_normal("block device at %s", pnp);
	return (UNCONF);
}

struct ld_vcimd_softc {
	struct  ld_softc sc_ld;
	struct vcimd_softc *sc_vcimd;
	struct buf *sc_buf;
	lwp_t *sc_lwp;
	kmutex_t sc_mtx;
	kcondvar_t sc_io_cv;
};

static void ld_vcimd_thread(void *);
static int ld_vcimd_dump(struct ld_softc *, void *, int, int);
static int ld_vcimd_start(struct ld_softc *, struct buf *);

static int
ld_vcimd_match(device_t parent, cfdata_t match, void *aux)
{

	return (1);
}

static void
ld_vcimd_attach(device_t parent, device_t self, void *aux)
{
	struct ld_vcimd_softc *sc = device_private(self);
	struct ld_softc *ld = &sc->sc_ld;
	sc->sc_vcimd = device_private(parent);
	sc->sc_buf = NULL;

	ld->sc_dv = self;
	ld->sc_maxxfer = MAXPHYS;
	ld->sc_maxqueuecnt = 1;
	ld->sc_secperunit = sc->sc_vcimd->sc_sectors;
	ld->sc_secsize = DEV_BSIZE;
	ld->sc_start = ld_vcimd_start;
	ld->sc_dump = ld_vcimd_dump;
	ld->sc_flags = LDF_ENABLED;

	aprint_normal("\n");
	mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_VM);
	cv_init(&sc->sc_io_cv, "vcimdio");

	if (kthread_create(PRI_BIO, KTHREAD_MPSAFE, NULL,
	    ld_vcimd_thread, sc, &sc->sc_lwp, "%s", device_xname(self))) {
		aprint_error_dev(self, "couldn't create task thread\n");
	    ld->sc_flags = 0;
	}
	ldattach(ld, BUFQ_DISK_DEFAULT_STRAT);
}

CFATTACH_DECL_NEW(ld_vcimd, sizeof(struct ld_vcimd_softc),
    ld_vcimd_match, ld_vcimd_attach, NULL, NULL);

static int
ld_vcimd_start(struct ld_softc *ld, struct buf *bp)
{
	struct ld_vcimd_softc *sc = (struct ld_vcimd_softc *)ld;

	mutex_enter(&sc->sc_mtx);
	KASSERT(sc->sc_buf == NULL);
	sc->sc_buf = bp;
	cv_broadcast(&sc->sc_io_cv);
	mutex_exit(&sc->sc_mtx);
	return 0;
}

static int
ld_vcimd_dump(struct ld_softc *ld, void *data, int blkno, int blkcnt)
{
	panic("ld_vcimd_dump: not implemented");
}

static void
ld_vcimd_thread(void * arg)
{
	paddr_t base;
	struct buf *bp;
	struct ld_vcimd_softc *sc = arg;

	mutex_enter(&sc->sc_mtx);
	for(;;) {
		while (sc->sc_buf == NULL)
			cv_wait(&sc->sc_io_cv, &sc->sc_mtx);
		bp = sc->sc_buf;
		sc->sc_buf = NULL;
		mutex_exit(&sc->sc_mtx);
		base = sc->sc_vcimd->sc_mdbase + bp->b_rawblkno * DEV_BSIZE;

		DPRINTF(("ld_vcimd_thread start %s %d bytes @%" PRId64 ": base 0x%" PRIx64, ((bp->b_flags & B_READ) == 0) ? "write" : "read", bp->b_bcount, bp->b_rawblkno, base));
		if ((bp->b_flags & B_READ) == 0) {
			/* write */
			tsarmips_memcpy_vaddr2paddr(base, (vaddr_t)bp->b_data, bp->b_bcount);
		} else {
			/* read */
			tsarmips_memcpy_paddr2vaddr((vaddr_t)bp->b_data, base, bp->b_bcount);
		}
		DPRINTF((" done\n"));
		bp->b_resid = 0;
		bp->b_error = 0;
		lddone(&sc->sc_ld, bp);
		mutex_enter(&sc->sc_mtx);
	}


}
