code:
/*	$OpenBSD: vpci.c,v 1.18 2014/12/09 06:58:29 doug Exp $	*/
/*
 * Copyright (c) 2008 Mark Kettenis <kettenis@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <uvm/uvm_extern.h>

#define _SPARC_BUS_DMA_PRIVATE
#include <machine/bus.h>
#include <machine/autoconf.h>
#include <machine/hypervisor.h>
#include <machine/openfirm.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>

#include <sparc64/dev/viommuvar.h>
#include <sparc64/dev/msivar.h>

extern struct sparc_pci_chipset _sparc_pci_chipset;

struct vpci_msi_msg {
	uint32_t	mm_version;
	uint8_t		mm_reserved[3];
	uint8_t		mm_type;
	uint64_t	mm_sysino;
	uint64_t	mm_reserved1;
	uint64_t	mm_stick;
	uint16_t	mm_reserved2[3];
	uint16_t	mm_reqid;
	uint64_t	mm_addr;
	uint64_t	mm_data;
	uint64_t	mm_reserved3;
};

struct vpci_range {
	u_int32_t	cspace;
	u_int32_t	child_hi;
	u_int32_t	child_lo;
	u_int32_t	phys_hi;
	u_int32_t	phys_lo;
	u_int32_t	size_hi;
	u_int32_t	size_lo;
};

struct vpci_pbm {
	struct vpci_softc *vp_sc;
	uint64_t vp_devhandle;

	struct vpci_range *vp_range;
	pci_chipset_tag_t vp_pc;
	int vp_nrange;

	bus_space_tag_t		vp_memt;
	bus_space_tag_t		vp_iot;
	bus_dma_tag_t		vp_dmat;
	struct iommu_state	vp_is;

	struct msi_eq *vp_meq;
	bus_addr_t vp_msiaddr;
	int vp_msinum;
	struct intrhand **vp_msi;

	int vp_flags;
};

struct vpci_softc {
	struct device sc_dv;
	bus_dma_tag_t sc_dmat;
	bus_space_tag_t sc_bust;
	int sc_node;
};

int vpci_match(struct device *, void *, void *);
void vpci_attach(struct device *, struct device *, void *);
void vpci_init_iommu(struct vpci_softc *, struct vpci_pbm *);
void vpci_init_msi(struct vpci_softc *, struct vpci_pbm *);
int vpci_print(void *, const char *);

pci_chipset_tag_t vpci_alloc_chipset(struct vpci_pbm *, int,
    pci_chipset_tag_t);
bus_space_tag_t vpci_alloc_mem_tag(struct vpci_pbm *);
bus_space_tag_t vpci_alloc_io_tag(struct vpci_pbm *);
bus_space_tag_t vpci_alloc_bus_tag(struct vpci_pbm *, const char *,
    int, int, int);
bus_dma_tag_t vpci_alloc_dma_tag(struct vpci_pbm *);

int vpci_conf_size(pci_chipset_tag_t, pcitag_t);
pcireg_t vpci_conf_read(pci_chipset_tag_t, pcitag_t, int);
void vpci_conf_write(pci_chipset_tag_t, pcitag_t, int, pcireg_t);

int vpci_intr_map(struct pci_attach_args *, pci_intr_handle_t *);
int vpci_bus_map(bus_space_tag_t, bus_space_tag_t, bus_addr_t,
    bus_size_t, int, bus_space_handle_t *);
paddr_t vpci_bus_mmap(bus_space_tag_t, bus_space_tag_t, bus_addr_t, off_t,
    int, int);
void *vpci_intr_establish(bus_space_tag_t, bus_space_tag_t, int, int, int,
    int (*)(void *), void *, const char *);
void vpci_intr_ack(struct intrhand *);
void vpci_msi_ack(struct intrhand *);

int vpci_msi_eq_intr(void *);

int vpci_dmamap_create(bus_dma_tag_t, bus_dma_tag_t, bus_size_t, int,
    bus_size_t, bus_size_t, int, bus_dmamap_t *);
void vpci_dmamap_destroy(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t);
int vpci_dmamap_load(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t,
    void *, bus_size_t, struct proc *, int);
void vpci_dmamap_unload(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t);
int vpci_dmamem_alloc(bus_dma_tag_t, bus_dma_tag_t, bus_size_t,
    bus_size_t, bus_size_t, bus_dma_segment_t *, int, int *, int);
int vpci_dmamem_map(bus_dma_tag_t, bus_dma_tag_t, bus_dma_segment_t *,
    int, size_t, caddr_t *, int);
void vpci_dmamem_unmap(bus_dma_tag_t, bus_dma_tag_t, caddr_t, size_t);

int
vpci_match(struct device *parent, void *match, void *aux)
{
	struct mainbus_attach_args *ma = aux;
	char compat[32];

	if (strcmp(ma->ma_name, "pci") != 0)
		return (0);

	if (OF_getprop(ma->ma_node, "compatible", compat, sizeof(compat)) == -1)
		return (0);

	if (strcmp(compat, "SUNW,sun4v-pci") == 0)
		return (1);

	return (0);
}

void
vpci_attach(struct device *parent, struct device *self, void *aux)
{
	struct vpci_softc *sc = (struct vpci_softc *)self;
	struct mainbus_attach_args *ma = aux;
	struct pcibus_attach_args pba;
	struct vpci_pbm *pbm;
	int *busranges = NULL, nranges;

	sc->sc_dmat = ma->ma_dmatag;
	sc->sc_bust = ma->ma_bustag;
	sc->sc_node = ma->ma_node;

	pbm = malloc(sizeof(*pbm), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (pbm == NULL)
		panic("vpci: can't alloc vpci pbm");

	pbm->vp_sc = sc;
	pbm->vp_devhandle = (ma->ma_reg[0].ur_paddr >> 32) & 0x0fffffff;

	if (getprop(ma->ma_node, "ranges", sizeof(struct vpci_range),
	    &pbm->vp_nrange, (void **)&pbm->vp_range))
		panic("vpci: can't get ranges");

	if (getprop(ma->ma_node, "bus-range", sizeof(int), &nranges,
	    (void **)&busranges))
		panic("vpci: can't get bus-range");

	printf(": bus %d to %d, ", busranges[0], busranges[1]);

	pbm->vp_memt = vpci_alloc_mem_tag(pbm);
	pbm->vp_iot = vpci_alloc_io_tag(pbm);
	pbm->vp_dmat = vpci_alloc_dma_tag(pbm);

	pbm->vp_pc = vpci_alloc_chipset(pbm, ma->ma_node, &_sparc_pci_chipset);
	pbm->vp_pc->bustag = pbm->vp_memt;

	vpci_init_iommu(sc, pbm);
	vpci_init_msi(sc, pbm);

	bzero(&pba, sizeof(pba));
	pba.pba_busname = "pci";
	pba.pba_domain = pci_ndomains++;
	pba.pba_bus = busranges[0];
	pba.pba_pc = pbm->vp_pc;
	pba.pba_flags = pbm->vp_flags;
	pba.pba_dmat = pbm->vp_dmat;
	pba.pba_memt = pbm->vp_memt;
	pba.pba_iot = pbm->vp_iot;
	pba.pba_pc->conf_size = vpci_conf_size;
	pba.pba_pc->conf_read = vpci_conf_read;
	pba.pba_pc->conf_write = vpci_conf_write;
	pba.pba_pc->intr_map = vpci_intr_map;

	free(busranges, M_DEVBUF, 0);

	config_found(&sc->sc_dv, &pba, vpci_print);
}

void
vpci_init_iommu(struct vpci_softc *sc, struct vpci_pbm *pbm)
{
	struct iommu_state *is = &pbm->vp_is;
	int tsbsize = 8;
	u_int32_t iobase = 0x80000000;
	char *name;

	name = (char *)malloc(32, M_DEVBUF, M_NOWAIT);
	if (name == NULL)
		panic("couldn't malloc iommu name");
	snprintf(name, 32, "%s dvma", sc->sc_dv.dv_xname);

	viommu_init(name, is, tsbsize, iobase);
	is->is_devhandle = pbm->vp_devhandle;
}

void
vpci_init_msi(struct vpci_softc *sc, struct vpci_pbm *pbm)
{
	u_int32_t msi_addr_range[3];
	u_int32_t msi_eq_devino[3] = { 0, 36, 24 };
	uint64_t sysino;
	int msis, msi_eq_size;
	int err;

	if (OF_getprop(sc->sc_node, "msi-address-ranges",
	    msi_addr_range, sizeof(msi_addr_range)) <= 0)
		return;
	pbm->vp_msiaddr = msi_addr_range[1];
	pbm->vp_msiaddr |= ((bus_addr_t)msi_addr_range[0]) << 32;

	msis = getpropint(sc->sc_node, "#msi", 256);
	pbm->vp_msi = mallocarray(msis, sizeof(*pbm->vp_msi), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (pbm->vp_msi == NULL)
		return;

	msi_eq_size = getpropint(sc->sc_node, "msi-eq-size", 256);
	pbm->vp_meq = msi_eq_alloc(sc->sc_dmat, msi_eq_size);
	if (pbm->vp_meq == NULL)
		goto free_table;

	err = hv_pci_msiq_conf(pbm->vp_devhandle, 0,
	    pbm->vp_meq->meq_map->dm_segs[0].ds_addr,
	    pbm->vp_meq->meq_nentries);
	if (err != H_EOK)
		goto free_queue;

	OF_getprop(sc->sc_node, "msi-eq-to-devino",
	    msi_eq_devino, sizeof(msi_eq_devino));
	err = hv_intr_devino_to_sysino(pbm->vp_devhandle,
	    msi_eq_devino[2], &sysino);
	if (err != H_EOK)
		goto disable_queue;

	if (vpci_intr_establish(sc->sc_bust, sc->sc_bust, sysino,
	    IPL_HIGH, 0, vpci_msi_eq_intr, pbm, sc->sc_dv.dv_xname) == NULL)
		goto disable_queue;

	err = hv_pci_msiq_setvalid(pbm->vp_devhandle, 0, PCI_MSIQ_VALID);
	if (err != H_EOK) {
		printf("%s: pci_msiq_setvalid: err %d\n", __func__, err);
		goto disable_queue;
	}

	err = hv_pci_msiq_setstate(pbm->vp_devhandle, 0, PCI_MSIQSTATE_IDLE);
	if (err != H_EOK) {
		printf("%s: pci_msiq_setstate: err %d\n", __func__, err);
		goto disable_queue;
	}

	pbm->vp_flags |= PCI_FLAGS_MSI_ENABLED;
	return;

disable_queue:
	hv_pci_msiq_conf(pbm->vp_devhandle, 0, 0, 0);
free_queue:
	msi_eq_free(sc->sc_dmat, pbm->vp_meq);
free_table:
	free(pbm->vp_msi, M_DEVBUF, 0);
}

int
vpci_print(void *aux, const char *p)
{
	if (p == NULL)
		return (UNCONF);
	return (QUIET);
}

int
vpci_conf_size(pci_chipset_tag_t pc, pcitag_t tag)
{
	return PCIE_CONFIG_SPACE_SIZE;
}

pcireg_t
vpci_conf_read(pci_chipset_tag_t pc, pcitag_t tag, int reg)
{
	struct vpci_pbm *pbm = pc->cookie;
	uint64_t error_flag, data;

	hv_pci_config_get(pbm->vp_devhandle, PCITAG_OFFSET(tag), reg, 4,
	    &error_flag, &data);

	return (error_flag ? (pcireg_t)~0 : data);
}

void
vpci_conf_write(pci_chipset_tag_t pc, pcitag_t tag, int reg, pcireg_t data)
{
	struct vpci_pbm *pbm = pc->cookie;
	uint64_t error_flag;

	hv_pci_config_put(pbm->vp_devhandle, PCITAG_OFFSET(tag), reg, 4,
            data, &error_flag);
}

/*
 * Bus-specific interrupt mapping
 */
int
vpci_intr_map(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
{
	struct vpci_pbm *pbm = pa->pa_pc->cookie;
	uint64_t devhandle = pbm->vp_devhandle;
	uint64_t devino = INTINO(*ihp);
	uint64_t sysino;
	int err;

	if (*ihp != (pci_intr_handle_t)-1) {
		err = hv_intr_devino_to_sysino(devhandle, devino, &sysino);
		if (err != H_EOK)
			return (-1);

		KASSERT(sysino == INTVEC(sysino));
		*ihp = sysino;
		return (0);
	}

	return (-1);
}

bus_space_tag_t
vpci_alloc_mem_tag(struct vpci_pbm *pp)
{
	return (vpci_alloc_bus_tag(pp, "mem",
	    0x02,       /* 32-bit mem space (where's the #define???) */
	    ASI_PRIMARY, ASI_PRIMARY_LITTLE));
}

bus_space_tag_t
vpci_alloc_io_tag(struct vpci_pbm *pp)
{
	return (vpci_alloc_bus_tag(pp, "io",
	    0x01,       /* IO space (where's the #define???) */
	    ASI_PRIMARY, ASI_PRIMARY_LITTLE));
}

bus_space_tag_t
vpci_alloc_bus_tag(struct vpci_pbm *pbm, const char *name, int ss,
    int asi, int sasi)
{
	struct vpci_softc *sc = pbm->vp_sc;
	struct sparc_bus_space_tag *bt;

	bt = malloc(sizeof(*bt), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (bt == NULL)
		panic("vpci: could not allocate bus tag");

	snprintf(bt->name, sizeof(bt->name), "%s-pbm_%s(%d/%2.2x)",
	    sc->sc_dv.dv_xname, name, ss, asi);

	bt->cookie = pbm;
	bt->parent = sc->sc_bust;
	bt->default_type = ss;
	bt->asi = asi;
	bt->sasi = sasi;
	bt->sparc_bus_map = vpci_bus_map;
	bt->sparc_bus_mmap = vpci_bus_mmap;
	bt->sparc_intr_establish = vpci_intr_establish;
	return (bt);
}

bus_dma_tag_t
vpci_alloc_dma_tag(struct vpci_pbm *pbm)
{
	struct vpci_softc *sc = pbm->vp_sc;
	bus_dma_tag_t dt, pdt = sc->sc_dmat;

	dt = malloc(sizeof(*dt), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (dt == NULL)
		panic("vpci: could not alloc dma tag");

	dt->_cookie = pbm;
	dt->_parent = pdt;
	dt->_dmamap_create = vpci_dmamap_create;
	dt->_dmamap_destroy = viommu_dvmamap_destroy;
	dt->_dmamap_load = viommu_dvmamap_load;
	dt->_dmamap_load_raw = viommu_dvmamap_load_raw;
	dt->_dmamap_unload = viommu_dvmamap_unload;
	dt->_dmamap_sync = viommu_dvmamap_sync;
	dt->_dmamem_alloc = viommu_dvmamem_alloc;
	dt->_dmamem_free = viommu_dvmamem_free;
	return (dt);
}

pci_chipset_tag_t
vpci_alloc_chipset(struct vpci_pbm *pbm, int node, pci_chipset_tag_t pc)
{
	pci_chipset_tag_t npc;

	npc = malloc(sizeof *npc, M_DEVBUF, M_NOWAIT);
	if (npc == NULL)
		panic("vpci: could not allocate pci_chipset_tag_t");
	memcpy(npc, pc, sizeof *pc);
	npc->cookie = pbm;
	npc->rootnode = node;
	return (npc);
}

#define BUS_DMA_FIND_PARENT(t, fn)                                      \
        if (t->_parent == NULL)                                         \
                panic("null bus_dma parent (" #fn ")");                 \
        for (t = t->_parent; t->fn == NULL; t = t->_parent)             \
                if (t->_parent == NULL)                                 \
                        panic("no bus_dma " #fn " located");

int
vpci_dmamap_create(bus_dma_tag_t t, bus_dma_tag_t t0, bus_size_t size,
    int nsegments, bus_size_t maxsegsz, bus_size_t boundary, int flags,
    bus_dmamap_t *dmamap)
{
	struct vpci_pbm *vp = t->_cookie;

	return (viommu_dvmamap_create(t, t0, &vp->vp_is, size, nsegments,
	    maxsegsz, boundary, flags, dmamap));
}

int
vpci_bus_map(bus_space_tag_t t, bus_space_tag_t t0, bus_addr_t offset,
    bus_size_t size, int flags, bus_space_handle_t *hp)
{
	struct vpci_pbm *pbm = t->cookie;
	int i, ss = t->default_type;

	if (t->parent == 0 || t->parent->sparc_bus_map == 0)
		panic("vpci_bus_map: invalid parent");

	if (flags & BUS_SPACE_MAP_PROMADDRESS) {
		return ((*t->parent->sparc_bus_map)
		    (t, t0, offset, size, flags, hp));
	}

	for (i = 0; i < pbm->vp_nrange; i++) {
		bus_addr_t paddr;

		if (((pbm->vp_range[i].cspace >> 24) & 0x03) != ss)
			continue;

		paddr = pbm->vp_range[i].phys_lo + offset;
		paddr |= ((bus_addr_t)pbm->vp_range[i].phys_hi) << 32;
		return ((*t->parent->sparc_bus_map)
		    (t, t0, paddr, size, flags, hp));
	}

	return (EINVAL);
}

paddr_t
vpci_bus_mmap(bus_space_tag_t t, bus_space_tag_t t0, bus_addr_t paddr,
    off_t off, int prot, int flags)
{
	bus_addr_t offset = paddr;
	struct vpci_pbm *pbm = t->cookie;
	int i, ss = t->default_type;

	if (t->parent == 0 || t->parent->sparc_bus_mmap == 0)
		panic("vpci_bus_mmap: invalid parent");

	for (i = 0; i < pbm->vp_nrange; i++) {
		bus_addr_t paddr;

		if (((pbm->vp_range[i].cspace >> 24) & 0x03) != ss)
			continue;

		paddr = pbm->vp_range[i].phys_lo + offset;
		paddr |= ((bus_addr_t)pbm->vp_range[i].phys_hi) << 32;
		return ((*t->parent->sparc_bus_mmap)
		    (t, t0, paddr, off, prot, flags));
	}

	return (-1);
}

void *
vpci_intr_establish(bus_space_tag_t t, bus_space_tag_t t0, int ihandle,
    int level, int flags, int (*handler)(void *), void *arg, const char *what)
{
	struct vpci_pbm *pbm = t->cookie;
	uint64_t sysino = INTVEC(ihandle);
	struct intrhand *ih;
	int err;

	ih = bus_intr_allocate(t0, handler, arg, ihandle, level,
	    NULL, NULL, what);
	if (ih == NULL)
		return (NULL);

	if (flags & BUS_INTR_ESTABLISH_MPSAFE)
		ih->ih_mpsafe = 1;

	if (ihandle & PCI_INTR_MSI) {
		pci_chipset_tag_t pc = pbm->vp_pc;
		pcitag_t tag = ihandle & ~PCI_INTR_MSI;
		int msinum = pbm->vp_msinum++;

		if (ih->ih_name)
			evcount_attach(&ih->ih_count, ih->ih_name, NULL);
		else
			evcount_attach(&ih->ih_count, "unknown", NULL);

		ih->ih_ack = vpci_msi_ack;

		pbm->vp_msi[msinum] = ih;
		ih->ih_number = msinum;

		pci_msi_enable(pc, tag, pbm->vp_msiaddr, msinum);

		err = hv_pci_msi_setmsiq(pbm->vp_devhandle, msinum, 0, 0);
		if (err != H_EOK) {
			printf("%s: pci_msi_setmsiq: err %d\n", __func__, err);
			return (NULL);
		}

		err = hv_pci_msi_setvalid(pbm->vp_devhandle, msinum, PCI_MSI_VALID);
		if (err != H_EOK) {
			printf("%s: pci_msi_setvalid: err %d\n", __func__, err);
			return (NULL);
		}

		err = hv_pci_msi_setstate(pbm->vp_devhandle, msinum, PCI_MSISTATE_IDLE);
		if (err != H_EOK) {
			printf("%s: pci_msi_setstate: err %d\n", __func__, err);
			return (NULL);
		}

		return (ih);
	}

	intr_establish(ih->ih_pil, ih);
	ih->ih_ack = vpci_intr_ack;

	err = hv_intr_settarget(sysino, cpus->ci_upaid);
	if (err != H_EOK)
		return (NULL);

	/* Clear pending interrupts. */
	err = hv_intr_setstate(sysino, INTR_IDLE);
	if (err != H_EOK)
		return (NULL);

	err = hv_intr_setenabled(sysino, INTR_ENABLED);
	if (err != H_EOK)
		return (NULL);

	return (ih);
}

void
vpci_intr_ack(struct intrhand *ih)
{
	hv_intr_setstate(ih->ih_number, INTR_IDLE);
}

void
vpci_msi_ack(struct intrhand *ih)
{
}

int
vpci_msi_eq_intr(void *arg)
{
	struct vpci_pbm *pbm = arg;
	struct msi_eq *meq = pbm->vp_meq;
	struct vpci_msi_msg *msg;
	uint64_t head, tail;
	struct intrhand *ih;
	int msinum;
	int err;

	err = hv_pci_msiq_gethead(pbm->vp_devhandle, 0, &head);
	if (err != H_EOK)
		printf("%s: pci_msiq_gethead: %d\n", __func__, err);

	err = hv_pci_msiq_gettail(pbm->vp_devhandle, 0, &tail);
	if (err != H_EOK)
		printf("%s: pci_msiq_gettail: %d\n", __func__, err);

	if (head == tail)
		return (0);

	while (head != tail) {
		msg = (struct vpci_msi_msg *)(meq->meq_va + head);

		if (msg->mm_type == 0)
			break;
		msg->mm_type = 0;

		msinum = msg->mm_data;
		ih = pbm->vp_msi[msinum];
		err = hv_pci_msi_setstate(pbm->vp_devhandle,
		    msinum, PCI_MSISTATE_IDLE);
		if (err != H_EOK)
			printf("%s: pci_msi_setstate: %d\n", __func__, err);

		send_softint(-1, ih->ih_pil, ih);

		head += sizeof(struct vpci_msi_msg);
		head &= ((meq->meq_nentries * sizeof(struct vpci_msi_msg)) - 1);
	}

	err = hv_pci_msiq_sethead(pbm->vp_devhandle, 0, head);
	if (err != H_EOK)
		printf("%s: pci_msiq_sethead: %d\n", __func__, err);

	return (1);
}

const struct cfattach vpci_ca = {
	sizeof(struct vpci_softc), vpci_match, vpci_attach
};

struct cfdriver vpci_cd = {
	NULL, "vpci", DV_DULL
};


code:
/*	$OpenBSD: vrng.c,v 1.4 2010/08/07 03:50:01 krw Exp $	*/
/*
 * Copyright (c) 2008 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/timeout.h>

#include <uvm/uvm_extern.h>

#include <machine/autoconf.h>
#include <machine/hypervisor.h>
#include <machine/openfirm.h>
#include <machine/sparc64.h>

#define HSVC_GROUP_RNG	0x104

#include <dev/rndvar.h>
#include <sparc64/dev/vbusvar.h>

struct rng_ctl {
	uint64_t rng_res : 39;
	uint64_t rng_wait_cnt : 16;
	uint64_t rng_bypass : 1;
	uint64_t rng_vcoctl_sel : 2;
	uint64_t rng_anlg_sel : 2;
	uint64_t rng_ctl4 : 1;
	uint64_t rng_ctl3 : 1;
	uint64_t rng_ctl2 : 1;
	uint64_t rng_ctl1 : 1;
};

struct vrng_softc {
	struct device sc_dv;
	struct timeout sc_to;
	int sc_count;
};

int	vrng_match(struct device *, void *, void *);
void	vrng_attach(struct device *, struct device *, void *);

struct cfattach vrng_ca = {
	sizeof(struct vrng_softc), vrng_match, vrng_attach
};

struct cfdriver vrng_cd = {
	NULL, "vrng", DV_DULL
};

void	vrng_rnd(void *);

int
vrng_match(struct device *parent, void *match, void *aux)
{
	struct vbus_attach_args *va = aux;

	if (strcmp(va->va_name, "random-number-generator") == 0)
		return (1);

	return (0);
}

void
vrng_attach(struct device *parent, struct device *self, void *aux)
{
	struct vrng_softc *sc = (void *)self;
	uint64_t supported_minor;
	struct rng_ctl ctl[4];
	uint64_t delta;
	paddr_t addr;
	int err;

	if (prom_set_sun4v_api_version(HSVC_GROUP_RNG, 1, 0, &supported_minor))
		printf(": unsupported hypervisor\n");

	err = hv_rng_get_diag_control();
	if (err != H_EOK && err != H_ENOACCESS)
		printf(": hv_rng_get_diag_control %d\n", err);

	/*
	 * If we're not the Trusted Domain, the hypervisor call above
	 * will fails with H_ENOACCESS.  In that case we hope that the
	 * RNG has been properly initialized.
	 */
	if (err == H_EOK) {
		bzero(ctl, sizeof(ctl));

		ctl[0].rng_ctl1 = 1;
		ctl[0].rng_vcoctl_sel = 0;
		ctl[0].rng_wait_cnt = 0x3e;

		ctl[1].rng_ctl2 = 1;
		ctl[1].rng_vcoctl_sel = 1;
		ctl[1].rng_wait_cnt = 0x3e;

		ctl[2].rng_ctl3 = 1;
		ctl[2].rng_vcoctl_sel = 2;
		ctl[2].rng_wait_cnt = 0x3e;

		ctl[3].rng_ctl1 = 1;
		ctl[3].rng_ctl2 = 1;
		ctl[3].rng_ctl3 = 1;
		ctl[3].rng_ctl4 = 1;
		ctl[3].rng_wait_cnt = 0x3e;

		if (!pmap_extract(pmap_kernel(), (vaddr_t)&ctl, &addr))
			panic("vrng_attach: pmap_extract failed");

		err = hv_rng_ctl_write(addr, RNG_STATE_CONFIGURED, 0, &delta);
		if (err != H_EOK)
			printf(": hv_rng_ctl_write %d\n", err);
	}

	printf("\n");

	timeout_set(&sc->sc_to, vrng_rnd, sc);
	vrng_rnd(sc);
}

void
vrng_rnd(void *v)
{
	struct vrng_softc *sc = v;
	uint64_t rnd;
	uint64_t delta;
	paddr_t addr;
	int err;

	if (!pmap_extract(pmap_kernel(), (vaddr_t)&rnd, &addr))
		panic("vrng_rnd: pmap_extract failed");
	err = hv_rng_data_read(addr, &delta);
	if (err == H_EOK) {
#if 0
		if ((sc->sc_count++ % 100) == 0)
			printf("vrng: %lx\n", rnd);
#endif
		add_true_randomness(rnd);
		add_true_randomness(rnd >> 32);
	}
	if (err != H_EOK && err != H_EWOULDBLOCK)
		printf("vrng_rnd: err = %d\n", err);
	else
		timeout_add(&sc->sc_to, 1);
}

license: unknown


code:
/*	$OpenBSD: vrtc.c,v 1.1 2008/03/08 19:19:43 kettenis Exp $	*/
/*
 * Copyright (c) 2008 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <machine/autoconf.h>
#include <machine/hypervisor.h>
#include <machine/openfirm.h>

#include <dev/clock_subr.h>
#include <sparc64/dev/vbusvar.h>

extern todr_chip_handle_t todr_handle;

int	vrtc_match(struct device *, void *, void *);
void	vrtc_attach(struct device *, struct device *, void *);

struct cfattach vrtc_ca = {
	sizeof(struct device), vrtc_match, vrtc_attach
};

struct cfdriver vrtc_cd = {
	NULL, "vrtc", DV_DULL
};

int	vrtc_gettime(todr_chip_handle_t, struct timeval *);
int	vrtc_settime(todr_chip_handle_t, struct timeval *);

int
vrtc_match(struct device *parent, void *match, void *aux)
{
	struct vbus_attach_args *va = aux;

	if (strcmp(va->va_name, "rtc") == 0)
		return (1);

	return (0);
}

void
vrtc_attach(struct device *parent, struct device *self, void *aux)
{
	todr_chip_handle_t handle;

	printf("\n");

	handle = malloc(sizeof(struct todr_chip_handle), M_DEVBUF,M_NOWAIT);
	if (handle == NULL)
		panic("couldn't allocate todr_handle");

	handle->cookie = self;
	handle->todr_gettime = vrtc_gettime;
	handle->todr_settime = vrtc_settime;

	handle->bus_cookie = NULL;
	handle->todr_setwen = NULL;
	todr_handle = handle;
}

int
vrtc_gettime(todr_chip_handle_t handle, struct timeval *tv)
{
	u_int64_t tod;

	if (hv_tod_get(&tod) != H_EOK)
		return (1);

	tv->tv_sec = tod;
	tv->tv_usec = 0;
	return (0);
}

int
vrtc_settime(todr_chip_handle_t handle, struct timeval *tv)
{
	if (hv_tod_set(tv->tv_sec) != H_EOK)
		return (1);

	return (0);
}


code:
/*	$OpenBSD: vsw.c,v 1.1 2009/05/16 10:53:06 kettenis Exp $	*/
/*
 * Copyright (c) 2009 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <machine/autoconf.h>
#include <machine/hypervisor.h>
#include <machine/mdesc.h>

#include <sparc64/dev/cbusvar.h>

#ifdef VSW_DEBUG
#define DPRINTF(x)	printf x
#else
#define DPRINTF(x)
#endif

struct vsw_softc {
	struct device	sc_dv;
	bus_space_tag_t	sc_bustag;
	bus_dma_tag_t	sc_dmatag;
};

int	vsw_match(struct device *, void *, void *);
void	vsw_attach(struct device *, struct device *, void *);

struct cfattach vsw_ca = {
	sizeof(struct vsw_softc), vsw_match, vsw_attach
};

struct cfdriver vsw_cd = {
	NULL, "vsw", DV_DULL
};

void	vsw_get_channel_endpoint(int, struct cbus_attach_args *);

int
vsw_match(struct device *parent, void *match, void *aux)
{
	struct cbus_attach_args *ca = aux;

	if (strcmp(ca->ca_name, "virtual-network-switch") == 0)
		return (1);

	return (0);
}

void
vsw_attach(struct device *parent, struct device *self, void *aux)
{
	struct cbus_attach_args *ca = aux;
	struct cbus_attach_args nca;
	struct md_header *hdr;
	struct md_element *elem;
	const char *name_blk;
	const char *str;
	int idx;
	int arc;

	printf("\n");

	hdr = (struct md_header *)mdesc;
	elem = (struct md_element *)(mdesc + sizeof(struct md_header));
	name_blk = mdesc + sizeof(struct md_header) + hdr->node_blk_sz;

	idx = ca->ca_idx;
	for (; elem[idx].tag != 'E'; idx++) {
		str = name_blk + elem[idx].name_offset;
		if (elem[idx].tag != 'a' || strcmp(str, "fwd") != 0)
			continue;

		arc = elem[idx].d.val;
		str = name_blk + elem[arc].name_offset;
		if (strcmp(str, "virtual-device-port") == 0) {
			bzero(&nca, sizeof(nca));
			nca.ca_name = "network";
			nca.ca_node = ca->ca_node;
			nca.ca_bustag = ca->ca_bustag;
			nca.ca_dmatag = ca->ca_dmatag;
			vsw_get_channel_endpoint(arc, &nca);
			config_found(self, &nca, cbus_print);
		}
	}
}

void
vsw_get_channel_endpoint(int idx, struct cbus_attach_args *ca)
{
	struct md_header *hdr;
	struct md_element *elem;
	const char *name_blk;
	const char *str;
	int arc;

	hdr = (struct md_header *)mdesc;
	elem = (struct md_element *)(mdesc + sizeof(struct md_header));
	name_blk = mdesc + sizeof(struct md_header) + hdr->node_blk_sz;

	ca->ca_idx = idx;

	ca->ca_id = -1;
	ca->ca_tx_ino = -1;
	ca->ca_rx_ino = -1;

	for (; elem[idx].tag != 'E'; idx++) {
		str = name_blk + elem[idx].name_offset;
		if (elem[idx].tag != 'a' || strcmp(str, "fwd") != 0)
			continue;

		arc = elem[idx].d.val;
		str = name_blk + elem[arc].name_offset;
		if (strcmp(str, "channel-endpoint") == 0) {
			ca->ca_id = mdesc_get_prop_val(arc, "id");
			ca->ca_tx_ino = mdesc_get_prop_val(arc, "tx-ino");
			ca->ca_rx_ino = mdesc_get_prop_val(arc, "rx-ino");
			return;
		}
	}
}


code:
/*	$OpenBSD: wbsd_ebus.c,v 1.1 2009/10/03 19:56:14 kettenis Exp $	*/
/*
 * Copyright (c) 2009 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/systm.h>

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

#include <sparc64/dev/ebusreg.h>
#include <sparc64/dev/ebusvar.h>

#include <dev/ic/w83l518dreg.h>
#include <dev/ic/w83l518dvar.h>
#include <dev/ic/w83l518d_sdmmc.h>

int	wbsd_ebus_match(struct device *, void *, void *);
void	wbsd_ebus_attach(struct device *, struct device *, void *);

struct cfattach wbsd_ebus_ca = {
	sizeof(struct wb_softc), wbsd_ebus_match, wbsd_ebus_attach
};

struct cfdriver wbsd_cd = {
	NULL, "wbsd", DV_DULL
};

int
wbsd_ebus_match(struct device *parent, void *match, void *aux)
{
	struct ebus_attach_args *ea = aux;

	if (strcmp(ea->ea_name, "TAD,wb-sdcard") == 0)
		return (1);

	return (0);
}

void
wbsd_ebus_attach(struct device *parent, struct device *self, void *aux)
{
	struct wb_softc *sc = (void *)self;
	struct ebus_attach_args *ea = aux;

	if (ebus_bus_map(ea->ea_iotag, 0,
	    EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
	    ea->ea_regs[0].size, 0, 0, &sc->wb_ioh) == 0) {
		sc->wb_iot = ea->ea_iotag;
	} else if (ebus_bus_map(ea->ea_memtag, 0,
	    EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
	    ea->ea_regs[0].size, 0, 0, &sc->wb_ioh) == 0) {
		sc->wb_iot = ea->ea_memtag;
	} else {
		printf(": can't map register space\n");
                return;
	}

	bus_intr_establish(sc->wb_iot, ea->ea_intrs[0], IPL_BIO, 0, wb_intr,
	    sc, self->dv_xname);

	printf("\n");

	sc->wb_type = WB_DEVNO_SD;
	wb_attach(sc);
}


code:
/*	$OpenBSD: z8530kbd.c,v 1.26 2013/05/10 16:00:08 mikeb Exp $	*/
/*	$NetBSD: z8530tty.c,v 1.77 2001/05/30 15:24:24 lukem Exp $	*/

/*-
 * Copyright (c) 1993, 1994, 1995, 1996, 1997, 1998, 1999
 *	Charles M. Hannum.  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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Charles M. Hannum.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

/*
 * Copyright (c) 1994 Gordon W. Ross
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *	@(#)zs.c	8.1 (Berkeley) 7/19/93
 */

/*
 * Zilog Z8530 Dual UART driver (tty interface)
 *
 * This is the "slave" driver that will be attached to
 * the "zsc" driver for plain "tty" async. serial lines.
 *
 * Credits, history:
 *
 * The original version of this code was the sparc/dev/zs.c driver
 * as distributed with the Berkeley 4.4 Lite release.  Since then,
 * Gordon Ross reorganized the code into the current parent/child
 * driver scheme, separating the Sun keyboard and mouse support
 * into independent child drivers.
 *
 * RTS/CTS flow-control support was a collaboration of:
 *	Gordon Ross <gwr@netbsd.org>,
 *	Bill Studenmund <wrstuden@loki.stanford.edu>
 *	Ian Dall <Ian.Dall@dsto.defence.gov.au>
 *
 * The driver was massively overhauled in November 1997 by Charles Hannum,
 * fixing *many* bugs, and substantially improving performance.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/syslog.h>

#include <machine/autoconf.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>

#include <dev/sun/sunkbdreg.h>
#include <dev/sun/sunkbdvar.h>

#include <dev/ic/z8530reg.h>
#include <machine/z8530var.h>

#include <dev/cons.h>

/*
 * How many input characters we can buffer.
 * The port-specific var.h may override this.
 * Note: must be a power of two!
 */
#ifndef	ZSKBD_RING_SIZE
#define	ZSKBD_RING_SIZE	2048
#endif

struct cfdriver zskbd_cd = {
	NULL, "zskbd", DV_TTY
};

/*
 * Make this an option variable one can patch.
 * But be warned:  this must be a power of 2!
 */
u_int zskbd_rbuf_size = ZSKBD_RING_SIZE;

/* Stop input when 3/4 of the ring is full; restart when only 1/4 is full. */
u_int zskbd_rbuf_hiwat = (ZSKBD_RING_SIZE * 1) / 4;
u_int zskbd_rbuf_lowat = (ZSKBD_RING_SIZE * 3) / 4;

struct zskbd_softc {
	struct sunkbd_softc	sc_base;

	struct	zs_chanstate *zst_cs;

	struct timeout zst_diag_ch;

	u_int zst_overflows,
	      zst_floods,
	      zst_errors;

	int zst_hwflags,	/* see z8530var.h */
	    zst_swflags;	/* TIOCFLAG_SOFTCAR, ... <ttycom.h> */

	u_int zst_r_hiwat,
	      zst_r_lowat;
	u_char *volatile zst_rbget,
	       *volatile zst_rbput;
	volatile u_int zst_rbavail;
	u_char *zst_rbuf,
	       *zst_ebuf;

	/*
	 * The transmit byte count and address are used for pseudo-DMA
	 * output in the hardware interrupt code.  PDMA can be suspended
	 * to get pending changes done; heldtbc is used for this.  It can
	 * also be stopped for ^S; this sets TS_TTSTOP in tp->t_state.
	 */
	u_char *zst_tba;		/* transmit buffer address */
	u_int zst_tbc,			/* transmit byte count */
	      zst_heldtbc;		/* held tbc while xmission stopped */

	u_char zst_tbuf[ZSKBD_RING_SIZE];
	u_char *zst_tbeg, *zst_tend, *zst_tbp;

	/* Flags to communicate with zskbd_softint() */
	volatile u_char zst_rx_flags,	/* receiver blocked */
#define	RX_TTY_BLOCKED		0x01
#define	RX_TTY_OVERFLOWED	0x02
#define	RX_IBUF_BLOCKED		0x04
#define	RX_IBUF_OVERFLOWED	0x08
#define	RX_ANY_BLOCK		0x0f
			zst_tx_busy,	/* working on an output chunk */
			zst_tx_done,	/* done with one output chunk */
			zst_tx_stopped,	/* H/W level stop (lost CTS) */
			zst_st_check,	/* got a status interrupt */
			zst_rx_ready;

	/* PPS signal on DCD, with or without inkernel clock disciplining */
	u_char  zst_ppsmask;			/* pps signal mask */
	u_char  zst_ppsassert;			/* pps leading edge */
	u_char  zst_ppsclear;			/* pps trailing edge */
};

/* Definition of the driver for autoconfig. */
static int	zskbd_match(struct device *, void *, void *);
static void	zskbd_attach(struct device *, struct device *, void *);

struct cfattach zskbd_ca = {
	sizeof(struct zskbd_softc), zskbd_match, zskbd_attach
};

struct zsops zsops_kbd;

static void zs_modem(struct zskbd_softc *, int);
static void zs_hwiflow(struct zskbd_softc *);
static void zs_maskintr(struct zskbd_softc *);

/* Low-level routines. */
static void zskbd_rxint(struct zs_chanstate *);
static void zskbd_stint(struct zs_chanstate *, int);
static void zskbd_txint(struct zs_chanstate *);
static void zskbd_softint(struct zs_chanstate *);
static void zskbd_diag(void *);

int zskbd_init(struct zskbd_softc *);
void zskbd_putc(struct zskbd_softc *, u_int8_t);
void zskbd_raw(struct zskbd_softc *, u_int8_t);

/* wskbd glue */
void zskbd_cngetc(void *, u_int *, int *);
void zskbd_cnpollc(void *, int);

void zsstart_tx(struct zskbd_softc *);
int zsenqueue_tx(void *, u_int8_t *, u_int);

struct wskbd_consops zskbd_consops = {
	zskbd_cngetc,
	zskbd_cnpollc
};

#define	ZSKBDUNIT(x)	(minor(x) & 0x7ffff)

/*
 * zskbd_match: how is this zs channel configured?
 */
int
zskbd_match(parent, vcf, aux)
	struct device *parent;
	void *vcf;
	void   *aux;
{
	struct cfdata *cf = vcf;
	struct zsc_attach_args *args = aux;
	int ret;

	/* If we're not looking for a keyboard, just exit */
	if (strcmp(args->type, "keyboard") != 0)
		return (0);

	ret = 10;

	/* Exact match is better than wildcard. */
	if (cf->cf_loc[ZSCCF_CHANNEL] == args->channel)
		ret += 2;

	/* This driver accepts wildcard. */
	if (cf->cf_loc[ZSCCF_CHANNEL] == ZSCCF_CHANNEL_DEFAULT)
		ret += 1;

	return (ret);
}

void
zskbd_attach(parent, self, aux)
	struct device *parent, *self;
	void   *aux;

{
	struct zsc_softc *zsc = (void *)parent;
	struct zskbd_softc *zst = (void *)self;
	struct sunkbd_softc *ss = (void *)self;
	struct cfdata *cf = self->dv_cfdata;
	struct zsc_attach_args *args = aux;
	struct wskbddev_attach_args a;
	struct zs_chanstate *cs;
	int channel, s, tty_unit, console = 0;
	dev_t dev;

	ss->sc_sendcmd = zsenqueue_tx;
	timeout_set(&ss->sc_bellto, sunkbd_bellstop, zst);

	timeout_set(&zst->zst_diag_ch, zskbd_diag, zst);

	zst->zst_tbp = zst->zst_tba = zst->zst_tbeg = zst->zst_tbuf;
	zst->zst_tend = zst->zst_tbeg + ZSKBD_RING_SIZE;

	tty_unit = ss->sc_dev.dv_unit;
	channel = args->channel;
	cs = zsc->zsc_cs[channel];
	cs->cs_private = zst;
	cs->cs_ops = &zsops_kbd;

	zst->zst_cs = cs;
	zst->zst_swflags = cf->cf_flags;	/* softcar, etc. */
	zst->zst_hwflags = args->hwflags;
	dev = makedev(zs_major, tty_unit);

	if (zst->zst_swflags)
		printf(" flags 0x%x", zst->zst_swflags);

	/*
	 * Check whether we serve as a console device.
	 * XXX - split console input/output channels aren't
	 *	 supported yet on /dev/console
	 */
	if ((zst->zst_hwflags & ZS_HWFLAG_CONSOLE_INPUT) != 0) {
		if ((args->hwflags & ZS_HWFLAG_USE_CONSDEV) != 0) {
			args->consdev->cn_dev = dev;
			cn_tab->cn_pollc = wskbd_cnpollc;
			cn_tab->cn_getc = wskbd_cngetc;
		}
		cn_tab->cn_dev = dev;
		console = 1;
	}

	zst->zst_rbuf = malloc(zskbd_rbuf_size << 1, M_DEVBUF, M_WAITOK);
	zst->zst_ebuf = zst->zst_rbuf + (zskbd_rbuf_size << 1);
	/* Disable the high water mark. */
	zst->zst_r_hiwat = 0;
	zst->zst_r_lowat = 0;
	zst->zst_rbget = zst->zst_rbput = zst->zst_rbuf;
	zst->zst_rbavail = zskbd_rbuf_size;

	/* if there are no enable/disable functions, assume the device
	   is always enabled */
	if (!cs->enable)
		cs->enabled = 1;

	/*
	 * Hardware init
	 */
	if (ISSET(zst->zst_hwflags, ZS_HWFLAG_CONSOLE)) {
		/* Call zsparam similar to open. */

		/* Wait a while for previous console output to complete */
		DELAY(10000);
	} else if (!ISSET(zst->zst_hwflags, ZS_HWFLAG_NORESET)) {
		/* Not the console; may need reset. */
		int reset;

		reset = (channel == 0) ? ZSWR9_A_RESET : ZSWR9_B_RESET;
		s = splzs();
		zs_write_reg(cs, 9, reset);
		splx(s);
	}

	/*
	 * Probe for a keyboard.
	 * If one is found, turn on receiver and status interrupts.
	 * We defer the actual write of the register to zsparam(),
	 * but we must make sure status interrupts are turned on by
	 * the time zsparam() reads the initial rr0 state.
	 */
	if (zskbd_init(zst)) {
		SET(cs->cs_preg[1], ZSWR1_RIE | ZSWR1_SIE);
		zs_write_reg(cs, 1, cs->cs_creg[1]);

		/* Make sure DTR is on now. */
		s = splzs();
		zs_modem(zst, 1);
		splx(s);
	} else {
		/* Will raise DTR in open. */
		s = splzs();
		zs_modem(zst, 0);
		splx(s);

		return;
	}

	ss->sc_click =
	    strcmp(getpropstring(optionsnode, "keyboard-click?"), "true") == 0;
	sunkbd_setclick(ss, ss->sc_click);

	a.console = console;
	if (ISTYPE5(ss->sc_layout)) {
		a.keymap = &sunkbd5_keymapdata;
#ifndef	SUNKBD5_LAYOUT
		if (ss->sc_layout < MAXSUNLAYOUT &&
		    sunkbd_layouts[ss->sc_layout] != -1)
			sunkbd5_keymapdata.layout =
			    sunkbd_layouts[ss->sc_layout];
#endif
	} else {
		a.keymap = &sunkbd_keymapdata;
#ifndef	SUNKBD_LAYOUT
		if (ss->sc_layout < MAXSUNLAYOUT &&
		    sunkbd_layouts[ss->sc_layout] != -1)
			sunkbd_keymapdata.layout =
			    sunkbd_layouts[ss->sc_layout];
#endif
	}
	a.accessops = &sunkbd_accessops;
	a.accesscookie = zst;

	if (console)
		wskbd_cnattach(&zskbd_consops, zst, a.keymap);

	sunkbd_attach(ss, &a);
}

int
zskbd_init(zst)
	struct zskbd_softc *zst;
{
	struct sunkbd_softc *ss = (void *)zst;
	struct zs_chanstate *cs = zst->zst_cs;
	int s, tries;
	u_int8_t v3, v4, v5, rr0;

	/* setup for 1200n81 */
	if (zs_set_speed(cs, 1200)) {			/* set 1200bps */
		printf(": failed to set baudrate\n");
		return 0;
	}
	if (zs_set_modes(cs, CS8 | CLOCAL)) {
		printf(": failed to set modes\n");
		return 0;
	}

	s = splzs();

	zs_maskintr(zst);

	v3 = cs->cs_preg[3];				/* set 8 bit chars */
	v5 = cs->cs_preg[5];
	CLR(v3, ZSWR3_RXSIZE);
	CLR(v5, ZSWR5_TXSIZE);
	SET(v3, ZSWR3_RX_8);
	SET(v5, ZSWR5_TX_8);
	cs->cs_preg[3] = v3;
	cs->cs_preg[5] = v5;

	v4 = cs->cs_preg[4];				/* no parity 1 stop */
	CLR(v4, ZSWR4_SBMASK | ZSWR4_PARMASK);
	SET(v4, ZSWR4_ONESB | ZSWR4_EVENP);
	cs->cs_preg[4] = v4;

	if (!cs->cs_heldchange) {
		if (zst->zst_tx_busy) {
			zst->zst_heldtbc = zst->zst_tbc;
			zst->zst_tbc = 0;
			cs->cs_heldchange = 1;
		} else
			zs_loadchannelregs(cs);
	}

	/*
	 * Hardware flow control is disabled, turn off the buffer water
	 * marks and unblock any soft flow control state.  Otherwise, enable
	 * the water marks.
	 */
	zst->zst_r_hiwat = 0;
	zst->zst_r_lowat = 0;
	if (ISSET(zst->zst_rx_flags, RX_TTY_OVERFLOWED)) {
		CLR(zst->zst_rx_flags, RX_TTY_OVERFLOWED);
		zst->zst_rx_ready = 1;
		cs->cs_softreq = 1;
	}
	if (ISSET(zst->zst_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED)) {
		CLR(zst->zst_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED);
		zs_hwiflow(zst);
	}

	/*
	 * Force a recheck of the hardware carrier and flow control status,
	 * since we may have changed which bits we're looking at.
	 */
	zskbd_stint(cs, 1);

	splx(s);

	/*
	 * Hardware flow control is disabled, unblock any hard flow control
	 * state.
	 */
	if (zst->zst_tx_stopped) {
		zst->zst_tx_stopped = 0;
		zsstart_tx(zst);
	}

	zskbd_softint(cs);

	/* Ok, start the reset sequence... */

	s = splhigh();

	for (tries = 5; tries != 0; tries--) {
		int ltries;

		ss->sc_leds = 0;
		ss->sc_layout = -1;

		/* Send reset request */
		zskbd_putc(zst, SKBD_CMD_RESET);

		ltries = 1000;
		while (--ltries > 0) {
			rr0 = *cs->cs_reg_csr;
			if (rr0 & ZSRR0_RX_READY) {
				sunkbd_raw(ss, *cs->cs_reg_data);
				if (ss->sc_kbdstate == SKBD_STATE_RESET)
					break;
			}
			DELAY(1000);
		}
		if (ltries == 0)
			continue;

		/* Wait for reset to finish. */
		ltries = 1000;
		while (--ltries > 0) {
			rr0 = *cs->cs_reg_csr;
			if (rr0 & ZSRR0_RX_READY) {
				sunkbd_raw(ss, *cs->cs_reg_data);
				if (ss->sc_kbdstate == SKBD_STATE_GETKEY)
					break;
			}
			DELAY(1000);
		}
		if (ltries == 0)
			continue;

		/* Some Sun<=>PS/2 converters need some delay here */
		DELAY(5000);

		/* Send layout request */
		zskbd_putc(zst, SKBD_CMD_LAYOUT);

		ltries = 1000;
		while (--ltries > 0) {
			rr0 = *cs->cs_reg_csr;
			if (rr0 & ZSRR0_RX_READY) {
				sunkbd_raw(ss, *cs->cs_reg_data);
				if (ss->sc_layout != -1)
					break;
			}
			DELAY(1000);
		}
		if (ltries == 0)
			continue;
		break;
	}
	if (tries == 0)
		printf(": no keyboard\n");
	else
		printf(": layout %d\n", ss->sc_layout);
	splx(s);

	return tries;
}

void
zskbd_putc(zst, c)
	struct zskbd_softc *zst;
	u_int8_t c;
{
	u_int8_t rr0;
	int s;

	s = splhigh();
	do {
		rr0 = *zst->zst_cs->cs_reg_csr;
	} while ((rr0 & ZSRR0_TX_READY) == 0);
	*zst->zst_cs->cs_reg_data = c;
	delay(2);
	splx(s);
}

int
zsenqueue_tx(v, str, len)
	void *v;
	u_int8_t *str;
	u_int len;
{
	struct zskbd_softc *zst = v;
	int s;
	u_int i;

	s = splzs();
	if (zst->zst_tbc + len > ZSKBD_RING_SIZE)
		return (-1);
	zst->zst_tbc += len;
	for (i = 0; i < len; i++) {
		*zst->zst_tbp = str[i];
		if (++zst->zst_tbp == zst->zst_tend)
			zst->zst_tbp = zst->zst_tbeg;
	}
	splx(s);
	zsstart_tx(zst);
	return (0);
}

void
zsstart_tx(zst)
	struct zskbd_softc *zst;
{
	struct zs_chanstate *cs = zst->zst_cs;
	int s, s1;

	s = spltty();

	if (zst->zst_tx_stopped)
		goto out;
	if (zst->zst_tbc == 0)
		goto out;

	s1 = splzs();

	zst->zst_tx_busy = 1;

	if (!ISSET(cs->cs_preg[1], ZSWR1_TIE)) {
		SET(cs->cs_preg[1], ZSWR1_TIE);
		cs->cs_creg[1] = cs->cs_preg[1];
		zs_write_reg(cs, 1, cs->cs_creg[1]);
	}

	zs_write_data(cs, *zst->zst_tba);

	zst->zst_tbc--;
	if (++zst->zst_tba == zst->zst_tend)
		zst->zst_tba = zst->zst_tbeg;

	splx(s1);

out:
	splx(s);
}

/*
 * Compute interrupt enable bits and set in the pending bits. Called both
 * in zsparam() and when PPS (pulse per second timing) state changes.
 * Must be called at splzs().
 */
static void
zs_maskintr(zst)
	struct zskbd_softc *zst;
{
	struct zs_chanstate *cs = zst->zst_cs;
	int tmp15;

	cs->cs_rr0_mask = cs->cs_rr0_cts | cs->cs_rr0_dcd;
	if (zst->zst_ppsmask != 0)
		cs->cs_rr0_mask |= cs->cs_rr0_pps;
	tmp15 = cs->cs_preg[15];
	if (ISSET(cs->cs_rr0_mask, ZSRR0_DCD))
		SET(tmp15, ZSWR15_DCD_IE);
	else
		CLR(tmp15, ZSWR15_DCD_IE);
	if (ISSET(cs->cs_rr0_mask, ZSRR0_CTS))
		SET(tmp15, ZSWR15_CTS_IE);
	else
		CLR(tmp15, ZSWR15_CTS_IE);
	cs->cs_preg[15] = tmp15;
}

/*
 * Raise or lower modem control (DTR/RTS) signals.  If a character is
 * in transmission, the change is deferred.
 */
static void
zs_modem(zst, onoff)
	struct zskbd_softc *zst;
	int onoff;
{
	struct zs_chanstate *cs = zst->zst_cs;

	if (cs->cs_wr5_dtr == 0)
		return;

	if (onoff)
		SET(cs->cs_preg[5], cs->cs_wr5_dtr);
	else
		CLR(cs->cs_preg[5], cs->cs_wr5_dtr);

	if (!cs->cs_heldchange) {
		if (zst->zst_tx_busy) {
			zst->zst_heldtbc = zst->zst_tbc;
			zst->zst_tbc = 0;
			cs->cs_heldchange = 1;
		} else
			zs_loadchannelregs(cs);
	}
}

/*
 * Internal version of zshwiflow
 * called at splzs
 */
static void
zs_hwiflow(zst)
	struct zskbd_softc *zst;
{
	struct zs_chanstate *cs = zst->zst_cs;

	if (cs->cs_wr5_rts == 0)
		return;

	if (ISSET(zst->zst_rx_flags, RX_ANY_BLOCK)) {
		CLR(cs->cs_preg[5], cs->cs_wr5_rts);
		CLR(cs->cs_creg[5], cs->cs_wr5_rts);
	} else {
		SET(cs->cs_preg[5], cs->cs_wr5_rts);
		SET(cs->cs_creg[5], cs->cs_wr5_rts);
	}
	zs_write_reg(cs, 5, cs->cs_creg[5]);
}

/****************************************************************
 * Interface to the lower layer (zscc)
 ****************************************************************/

#define	integrate
integrate void zskbd_rxsoft(struct zskbd_softc *);
integrate void zskbd_txsoft(struct zskbd_softc *);
integrate void zskbd_stsoft(struct zskbd_softc *);
/*
 * receiver ready interrupt.
 * called at splzs
 */
static void
zskbd_rxint(cs)
	struct zs_chanstate *cs;
{
	struct zskbd_softc *zst = cs->cs_private;
	u_char *put, *end;
	u_int cc;
	u_char rr0, rr1, c;

	end = zst->zst_ebuf;
	put = zst->zst_rbput;
	cc = zst->zst_rbavail;

	while (cc > 0) {
		/*
		 * First read the status, because reading the received char
		 * destroys the status of this char.
		 */
		rr1 = zs_read_reg(cs, 1);
		c = zs_read_data(cs);

		if (ISSET(rr1, ZSRR1_FE | ZSRR1_DO | ZSRR1_PE)) {
			/* Clear the receive error. */
			zs_write_csr(cs, ZSWR0_RESET_ERRORS);
		}

		put[0] = c;
		put[1] = rr1;
		put += 2;
		if (put >= end)
			put = zst->zst_rbuf;
		cc--;

		rr0 = zs_read_csr(cs);
		if (!ISSET(rr0, ZSRR0_RX_READY))
			break;
	}

	/*
	 * Current string of incoming characters ended because
	 * no more data was available or we ran out of space.
	 * Schedule a receive event if any data was received.
	 * If we're out of space, turn off receive interrupts.
	 */
	zst->zst_rbput = put;
	zst->zst_rbavail = cc;
	if (!ISSET(zst->zst_rx_flags, RX_TTY_OVERFLOWED)) {
		zst->zst_rx_ready = 1;
		cs->cs_softreq = 1;
	}

	/*
	 * See if we are in danger of overflowing a buffer. If
	 * so, use hardware flow control to ease the pressure.
	 */
	if (!ISSET(zst->zst_rx_flags, RX_IBUF_BLOCKED) &&
	    cc < zst->zst_r_hiwat) {
		SET(zst->zst_rx_flags, RX_IBUF_BLOCKED);
		zs_hwiflow(zst);
	}

	/*
	 * If we're out of space, disable receive interrupts
	 * until the queue has drained a bit.
	 */
	if (!cc) {
		SET(zst->zst_rx_flags, RX_IBUF_OVERFLOWED);
		CLR(cs->cs_preg[1], ZSWR1_RIE);
		cs->cs_creg[1] = cs->cs_preg[1];
		zs_write_reg(cs, 1, cs->cs_creg[1]);
	}
}

/*
 * transmitter ready interrupt.  (splzs)
 */
static void
zskbd_txint(cs)
	struct zs_chanstate *cs;
{
	struct zskbd_softc *zst = cs->cs_private;

	/*
	 * If we've delayed a parameter change, do it now, and restart
	 * output.
	 */
	if (cs->cs_heldchange) {
		zs_loadchannelregs(cs);
		cs->cs_heldchange = 0;
		zst->zst_tbc = zst->zst_heldtbc;
		zst->zst_heldtbc = 0;
	}

	/* Output the next character in the buffer, if any. */
	if (zst->zst_tbc > 0) {
		zs_write_data(cs, *zst->zst_tba);
		zst->zst_tbc--;
		if (++zst->zst_tba == zst->zst_tend)
			zst->zst_tba = zst->zst_tbeg;
	} else {
		/* Disable transmit completion interrupts if necessary. */
		if (ISSET(cs->cs_preg[1], ZSWR1_TIE)) {
			CLR(cs->cs_preg[1], ZSWR1_TIE);
			cs->cs_creg[1] = cs->cs_preg[1];
			zs_write_reg(cs, 1, cs->cs_creg[1]);
		}
		if (zst->zst_tx_busy) {
			zst->zst_tx_busy = 0;
			zst->zst_tx_done = 1;
			cs->cs_softreq = 1;
		}
	}
}

/*
 * status change interrupt.  (splzs)
 */
static void
zskbd_stint(cs, force)
	struct zs_chanstate *cs;
	int force;
{
	struct zskbd_softc *zst = cs->cs_private;
	u_char rr0, delta;

	rr0 = zs_read_csr(cs);
	zs_write_csr(cs, ZSWR0_RESET_STATUS);

	/*
	 * Check here for console break, so that we can abort
	 * even when interrupts are locking up the machine.
	 */
	if (!force)
		delta = rr0 ^ cs->cs_rr0;
	else
		delta = cs->cs_rr0_mask;
	cs->cs_rr0 = rr0;

	if (ISSET(delta, cs->cs_rr0_mask)) {
		SET(cs->cs_rr0_delta, delta);

		/*
		 * Stop output immediately if we lose the output
		 * flow control signal or carrier detect.
		 */
		if (ISSET(~rr0, cs->cs_rr0_mask)) {
			zst->zst_tbc = 0;
			zst->zst_heldtbc = 0;
		}

		zst->zst_st_check = 1;
		cs->cs_softreq = 1;
	}
}

void
zskbd_diag(arg)
	void *arg;
{
	struct zskbd_softc *zst = arg;
	struct sunkbd_softc *ss = arg;
	int overflows, floods;
	int s;

	s = splzs();
	overflows = zst->zst_overflows;
	zst->zst_overflows = 0;
	floods = zst->zst_floods;
	zst->zst_floods = 0;
	zst->zst_errors = 0;
	splx(s);

	log(LOG_WARNING, "%s: %d silo overflow%s, %d ibuf flood%s\n",
	    ss->sc_dev.dv_xname,
	    overflows, overflows == 1 ? "" : "s",
	    floods, floods == 1 ? "" : "s");
}

integrate void
zskbd_rxsoft(zst)
	struct zskbd_softc *zst;
{
	struct sunkbd_softc *ss = (void *)zst;
	struct zs_chanstate *cs = zst->zst_cs;
	u_char *get, *end;
	u_int cc, scc;
	u_char rr1;
	int code;
	int s;
	u_int8_t cbuf[SUNKBD_MAX_INPUT_SIZE], *c;

	end = zst->zst_ebuf;
	get = zst->zst_rbget;
	scc = cc = zskbd_rbuf_size - zst->zst_rbavail;

	if (cc == zskbd_rbuf_size) {
		zst->zst_floods++;
		if (zst->zst_errors++ == 0)
			timeout_add_sec(&zst->zst_diag_ch, 60);
	}

	c = cbuf;
	while (cc) {
		code = get[0];
		rr1 = get[1];
		if (ISSET(rr1, ZSRR1_DO | ZSRR1_FE | ZSRR1_PE)) {
			if (ISSET(rr1, ZSRR1_DO)) {
				zst->zst_overflows++;
				if (zst->zst_errors++ == 0)
					timeout_add_sec(&zst->zst_diag_ch, 60);
			}
			if (ISSET(rr1, ZSRR1_FE))
				SET(code, TTY_FE);
			if (ISSET(rr1, ZSRR1_PE))
				SET(code, TTY_PE);
		}

		*c++ = code;
		if (c - cbuf == sizeof cbuf) {
			sunkbd_input(ss, cbuf, c - cbuf);
			c = cbuf;
		}

		get += 2;
		if (get >= end)
			get = zst->zst_rbuf;
		cc--;
	}
	if (c != cbuf)
		sunkbd_input(ss, cbuf, c - cbuf);

	if (cc != scc) {
		zst->zst_rbget = get;
		s = splzs();
		cc = zst->zst_rbavail += scc - cc;
		/* Buffers should be ok again, release possible block. */
		if (cc >= zst->zst_r_lowat) {
			if (ISSET(zst->zst_rx_flags, RX_IBUF_OVERFLOWED)) {
				CLR(zst->zst_rx_flags, RX_IBUF_OVERFLOWED);
				SET(cs->cs_preg[1], ZSWR1_RIE);
				cs->cs_creg[1] = cs->cs_preg[1];
				zs_write_reg(cs, 1, cs->cs_creg[1]);
			}
			if (ISSET(zst->zst_rx_flags, RX_IBUF_BLOCKED)) {
				CLR(zst->zst_rx_flags, RX_IBUF_BLOCKED);
				zs_hwiflow(zst);
			}
		}
		splx(s);
	}
}

integrate void
zskbd_txsoft(zst)
	struct zskbd_softc *zst;
{
}

integrate void
zskbd_stsoft(zst)
	struct zskbd_softc *zst;
{
	struct zs_chanstate *cs = zst->zst_cs;
	u_char rr0, delta;
	int s;

	s = splzs();
	rr0 = cs->cs_rr0;
	delta = cs->cs_rr0_delta;
	cs->cs_rr0_delta = 0;
	splx(s);

	if (ISSET(delta, cs->cs_rr0_cts)) {
		/* Block or unblock output according to flow control. */
		if (ISSET(rr0, cs->cs_rr0_cts))
			zst->zst_tx_stopped = 0;
		else
			zst->zst_tx_stopped = 1;
	}
}

/*
 * Software interrupt.  Called at zssoft
 *
 * The main job to be done here is to empty the input ring
 * by passing its contents up to the tty layer.  The ring is
 * always emptied during this operation, therefore the ring
 * must not be larger than the space after "high water" in
 * the tty layer, or the tty layer might drop our input.
 *
 * Note: an "input blockage" condition is assumed to exist if
 * EITHER the TS_TBLOCK flag or zst_rx_blocked flag is set.
 */
static void
zskbd_softint(cs)
	struct zs_chanstate *cs;
{
	struct zskbd_softc *zst = cs->cs_private;
	int s;

	s = spltty();

	if (zst->zst_rx_ready) {
		zst->zst_rx_ready = 0;
		zskbd_rxsoft(zst);
	}

	if (zst->zst_st_check) {
		zst->zst_st_check = 0;
		zskbd_stsoft(zst);
	}

	if (zst->zst_tx_done) {
		zst->zst_tx_done = 0;
		zskbd_txsoft(zst);
	}

	splx(s);
}

struct zsops zsops_kbd = {
	zskbd_rxint,	/* receive char available */
	zskbd_stint,	/* external/status */
	zskbd_txint,	/* xmit buffer empty */
	zskbd_softint,	/* process software interrupt */
};

void
zskbd_cnpollc(v, on)
	void *v;
	int on;
{
	extern int swallow_zsintrs;

	if (on)
		swallow_zsintrs++;
	else
		swallow_zsintrs--;
}

void
zskbd_cngetc(v, type, data)
	void *v;
	u_int *type;
	int *data;
{
	struct zskbd_softc *zst = v;
	int s;
	u_int8_t c, rr0;

	s = splhigh();
	do {
		rr0 = *zst->zst_cs->cs_reg_csr;
	} while ((rr0 & ZSRR0_RX_READY) == 0);

	c = *zst->zst_cs->cs_reg_data;
	splx(s);

	sunkbd_decode(c, type, data);
}


code:
/*	$OpenBSD: zs.c,v 1.27 2015/02/05 12:04:58 miod Exp $	*/
/*	$NetBSD: zs.c,v 1.29 2001/05/30 15:24:24 lukem Exp $	*/

/*-
 * Copyright (c) 1996 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Gordon W. Ross.
 *
 * 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.
 */

/*
 * Zilog Z8530 Dual UART driver (machine-dependent part)
 *
 * Runs two serial lines per chip using slave drivers.
 * Plain tty/async lines use the zstty slave.
 * Sun keyboard/mouse uses the zskbd/zsms slaves.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/syslog.h>

#include <machine/autoconf.h>
#include <machine/openfirm.h>
#include <machine/conf.h>
#include <machine/cpu.h>
#include <machine/psl.h>
#include <machine/z8530var.h>

#include <dev/cons.h>
#include <dev/ic/z8530reg.h>
#include <sparc64/dev/fhcvar.h>
#include <ddb/db_output.h>

#include <sparc64/dev/cons.h>

struct cfdriver zs_cd = {
	NULL, "zs", DV_TTY
};

/*
 * Some warts needed by z8530tty.c -
 * The default parity REALLY needs to be the same as the PROM uses,
 * or you can not see messages done with printf during boot-up...
 */
int zs_def_cflag = (CREAD | CS8 | HUPCL);
int zs_major = 12;

/*
 * The Sun provides a 4.9152 MHz clock to the ZS chips.
 */
#define PCLK	(9600 * 512)	/* PCLK pin input clock rate */

#define	ZS_DELAY()

/* The layout of this is hardware-dependent (padding, order). */
struct zschan {
	volatile u_char	zc_csr;		/* ctrl,status, and indirect access */
	u_char		zc_xxx0;
	volatile u_char	zc_data;	/* data */
	u_char		zc_xxx1;
};
struct zsdevice {
	/* Yes, they are backwards. */
	struct	zschan zs_chan_b;
	struct	zschan zs_chan_a;
};

/* ZS channel used as the console device (if any) */
void *zs_conschan_get, *zs_conschan_put;

static u_char zs_init_reg[16] = {
	0,	/* 0: CMD (reset, etc.) */
	0,	/* 1: No interrupts yet. */
	0,	/* 2: IVECT */
	ZSWR3_RX_8 | ZSWR3_RX_ENABLE,
	ZSWR4_CLK_X16 | ZSWR4_ONESB,
	ZSWR5_TX_8 | ZSWR5_TX_ENABLE,
	0,	/* 6: TXSYNC/SYNCLO */
	0,	/* 7: RXSYNC/SYNCHI */
	0,	/* 8: alias for data port */
	ZSWR9_MASTER_IE | ZSWR9_NO_VECTOR,
	0,	/*10: Misc. TX/RX control bits */
	ZSWR11_TXCLK_BAUD | ZSWR11_RXCLK_BAUD,
	((PCLK/32)/9600)-2,	/*12: BAUDLO (default=9600) */
	0,			/*13: BAUDHI (default=9600) */
	ZSWR14_BAUD_ENA | ZSWR14_BAUD_FROM_PCLK,
	ZSWR15_BREAK_IE,
};

/* Console ops */
static int  zscngetc(dev_t);
static void zscnputc(dev_t, int);
static void zscnpollc(dev_t, int);

struct consdev zs_consdev = {
	NULL,
	NULL,
	zscngetc,
	zscnputc,
	zscnpollc,
	NULL,
};

/****************************************************************
 * Autoconfig
 ****************************************************************/

/* Definition of the driver for autoconfig. */
static int  zs_match_sbus(struct device *, void *, void *);
static void zs_attach_sbus(struct device *, struct device *, void *);

static int  zs_match_fhc(struct device *, void *, void *);
static void zs_attach_fhc(struct device *, struct device *, void *);

static void zs_attach(struct zsc_softc *, struct zsdevice *, int);
static int  zs_print(void *, const char *name);

struct cfattach zs_sbus_ca = {
	sizeof(struct zsc_softc), zs_match_sbus, zs_attach_sbus
};

struct cfattach zs_fhc_ca = {
	sizeof(struct zsc_softc), zs_match_fhc, zs_attach_fhc
};

extern int stdinnode;
extern int fbnode;

/* Interrupt handlers. */
static int zshard(void *);
static void zssoft(void *);

static int zs_get_speed(struct zs_chanstate *);

/* Console device support */
static int zs_console_flags(int, int, int);

/*
 * Is the zs chip present?
 */
static int
zs_match_sbus(parent, vcf, aux)
	struct device *parent;
	void *vcf;
	void *aux;
{
	struct cfdata *cf = vcf;
	struct sbus_attach_args *sa = aux;

	if (strcmp(cf->cf_driver->cd_name, sa->sa_name) != 0)
		return (0);

	return (1);
}

static int
zs_match_fhc(parent, vcf, aux)
	struct device *parent;
	void *vcf;
	void *aux;
{
	struct cfdata *cf = vcf;
	struct fhc_attach_args *fa = aux;

	if (strcmp(cf->cf_driver->cd_name, fa->fa_name) != 0)
		return (0);
	return (1);
}

static void
zs_attach_sbus(parent, self, aux)
	struct device *parent;
	struct device *self;
	void *aux;
{
	struct zsc_softc *zsc = (void *) self;
	struct sbus_attach_args *sa = aux;
	struct zsdevice *zsaddr;
	bus_space_handle_t kvaddr;

	if (sa->sa_nintr == 0) {
		printf(" no interrupt lines\n");
		return;
	}

	/* Only map registers once. */
	if (sa->sa_npromvaddrs) {
		/*
		 * We're converting from a 32-bit pointer to a 64-bit
		 * pointer.  Since the 32-bit entity is negative, but
		 * the kernel is still mapped into the lower 4GB
		 * range, this needs to be zero-extended.
		 *
		 * XXXXX If we map the kernel and devices into the
		 * high 4GB range, this needs to be changed to
		 * sign-extend the address.
		 */
		zsaddr = (struct zsdevice *)
		    (unsigned long int)sa->sa_promvaddrs[0];
	} else {
		if (sbus_bus_map(sa->sa_bustag, sa->sa_slot, sa->sa_offset,
		    sa->sa_size, BUS_SPACE_MAP_LINEAR, 0, &kvaddr) != 0) {
			printf("%s @ sbus: cannot map registers\n",
			       self->dv_xname);
			return;
		}
		zsaddr = (struct zsdevice *)
		    bus_space_vaddr(sa->sa_bustag, kvaddr);
	}

	zsc->zsc_bustag = sa->sa_bustag;
	zsc->zsc_dmatag = sa->sa_dmatag;
	zsc->zsc_promunit = getpropint(sa->sa_node, "slave", -2);
	zsc->zsc_node = sa->sa_node;

	zs_attach(zsc, zsaddr, sa->sa_pri);
}

static void
zs_attach_fhc(parent, self, aux)
	struct device *parent;
	struct device *self;
	void *aux;
{
	struct zsc_softc *zsc = (void *) self;
	struct fhc_attach_args *fa = aux;
	struct zsdevice *zsaddr;
	bus_space_handle_t kvaddr;

	if (fa->fa_nreg < 1 && fa->fa_npromvaddrs < 1) {
		printf(": no registers\n");
		return;
	}

	if (fa->fa_nintr < 1) {
		printf(": no interrupts\n");
		return;
	}

	if (fa->fa_npromvaddrs) {
		/*
		 * We're converting from a 32-bit pointer to a 64-bit
		 * pointer.  Since the 32-bit entity is negative, but
		 * the kernel is still mapped into the lower 4GB
		 * range, this needs to be zero-extended.
		 *
		 * XXXXX If we map the kernel and devices into the
		 * high 4GB range, this needs to be changed to
		 * sign-extend the address.
		 */
		zsaddr = (struct zsdevice *)
		    (unsigned long int)fa->fa_promvaddrs[0];
	} else {
		if (fhc_bus_map(fa->fa_bustag, fa->fa_reg[0].fbr_slot,
		    fa->fa_reg[0].fbr_offset, fa->fa_reg[0].fbr_size,
		    BUS_SPACE_MAP_LINEAR, &kvaddr) != 0) {
			printf("%s @ fhc: cannot map registers\n",
			    self->dv_xname);
			return;
		}
		zsaddr = (struct zsdevice *)
		    bus_space_vaddr(fa->fa_bustag, kvaddr);
	}

	zsc->zsc_bustag = fa->fa_bustag;
	zsc->zsc_dmatag = NULL;
	zsc->zsc_promunit = getpropint(fa->fa_node, "slave", -2);
	zsc->zsc_node = fa->fa_node;

	zs_attach(zsc, zsaddr, fa->fa_intr[0]);
}

/*
 * Attach a found zs.
 *
 * USE ROM PROPERTY keyboard FOR KEYBOARD/MOUSE?
 */
static void
zs_attach(zsc, zsd, pri)
	struct zsc_softc *zsc;
	struct zsdevice *zsd;
	int pri;
{
	struct zsc_attach_args zsc_args;
	struct zs_chanstate *cs;
	int s, channel, softpri = PIL_TTY;

	if (zsd == NULL) {
		printf("configuration incomplete\n");
		return;
	}

	printf(" softpri %d\n", softpri);

	/*
	 * Initialize software state for each channel.
	 */
	for (channel = 0; channel < 2; channel++) {
		struct zschan *zc;
		struct device *child;

		zsc_args.type = "serial";
		if (getproplen(zsc->zsc_node, "keyboard") == 0) {
			if (channel == 0)
				zsc_args.type = "keyboard";
			if (channel == 1)
				zsc_args.type = "mouse";
		}

		zsc_args.channel = channel;
		cs = &zsc->zsc_cs_store[channel];
		zsc->zsc_cs[channel] = cs;

		cs->cs_channel = channel;
		cs->cs_private = NULL;
		cs->cs_ops = &zsops_null;
		cs->cs_brg_clk = PCLK / 16;

		zc = (channel == 0) ? &zsd->zs_chan_a : &zsd->zs_chan_b;

		zsc_args.consdev = NULL;
		zsc_args.hwflags = zs_console_flags(zsc->zsc_promunit,
						    zsc->zsc_node,
						    channel);

		if (zsc_args.hwflags & ZS_HWFLAG_CONSOLE) {
			zsc_args.hwflags |= ZS_HWFLAG_USE_CONSDEV;
			zsc_args.consdev = &zs_consdev;
		}

		if (getproplen(zsc->zsc_node, channel == 0 ?
		    "port-a-ignore-cd" : "port-b-ignore-cd") == 0) {
			zsc_args.hwflags |= ZS_HWFLAG_NO_DCD;
		}

		if ((zsc_args.hwflags & ZS_HWFLAG_CONSOLE_INPUT) != 0) {
			zs_conschan_get = zc;
		}
		if ((zsc_args.hwflags & ZS_HWFLAG_CONSOLE_OUTPUT) != 0) {
			zs_conschan_put = zc;
		}
		/* Childs need to set cn_dev, etc */

		cs->cs_reg_csr  = &zc->zc_csr;
		cs->cs_reg_data = &zc->zc_data;

		bcopy(zs_init_reg, cs->cs_creg, 16);
		bcopy(zs_init_reg, cs->cs_preg, 16);

		/* XXX: Consult PROM properties for this?! */
		cs->cs_defspeed = zs_get_speed(cs);
		cs->cs_defcflag = zs_def_cflag;

		/* Make these correspond to cs_defcflag (-crtscts) */
		cs->cs_rr0_dcd = ZSRR0_DCD;
		cs->cs_rr0_cts = 0;
		cs->cs_wr5_dtr = ZSWR5_DTR | ZSWR5_RTS;
		cs->cs_wr5_rts = 0;

		/*
		 * Clear the master interrupt enable.
		 * The INTENA is common to both channels,
		 * so just do it on the A channel.
		 */
		if (channel == 0) {
			zs_write_reg(cs, 9, 0);
		}

		/*
		 * Look for a child driver for this channel.
		 * The child attach will setup the hardware.
		 */
		if (!(child =
		      config_found(&zsc->zsc_dev, (void *)&zsc_args, zs_print))) {
			/* No sub-driver.  Just reset it. */
			u_char reset = (channel == 0) ?
				ZSWR9_A_RESET : ZSWR9_B_RESET;
			s = splzs();
			zs_write_reg(cs,  9, reset);
			splx(s);
		}
	}

	/*
	 * Now safe to install interrupt handlers.
	 */
	if (bus_intr_establish(zsc->zsc_bustag, pri, IPL_SERIAL, 0, zshard,
	    zsc, zsc->zsc_dev.dv_xname) == NULL)
		panic("zsattach: could not establish interrupt");
	if (!(zsc->zsc_softintr = softintr_establish(softpri, zssoft, zsc)))
		panic("zsattach: could not establish soft interrupt");

	/*
	 * Set the master interrupt enable and interrupt vector.
	 * (common to both channels, do it on A)
	 */
	cs = zsc->zsc_cs[0];
	s = splhigh();
	/* interrupt vector */
	zs_write_reg(cs, 2, zs_init_reg[2]);
	/* master interrupt control (enable) */
	zs_write_reg(cs, 9, zs_init_reg[9]);
	splx(s);

}

static int
zs_print(aux, name)
	void *aux;
	const char *name;
{
	struct zsc_attach_args *args = aux;

	if (name != NULL)
		printf("%s: ", name);

	if (args->channel != -1)
		printf(" channel %d", args->channel);

	return (UNCONF);
}

static int
zshard(arg)
	void *arg;
{
	struct zsc_softc *zsc = (struct zsc_softc *)arg;
	int rval = 0;

	while (zsc_intr_hard(zsc))
		rval = 1;
	if ((zsc->zsc_cs[0] && zsc->zsc_cs[0]->cs_softreq) ||
	    (zsc->zsc_cs[1] && zsc->zsc_cs[1]->cs_softreq))
		softintr_schedule(zsc->zsc_softintr);
	return (rval);
}

/*
 * We need this only for TTY_DEBUG purposes.
 */
static void
zssoft(arg)
	void *arg;
{
	struct zsc_softc *zsc = (struct zsc_softc *)arg;
	int s;

	/* Make sure we call the tty layer at spltty. */
	s = spltty();
	(void)zsc_intr_soft(zsc);
#ifdef TTY_DEBUG
	{
		struct zstty_softc *zst0 = zsc->zsc_cs[0]->cs_private;
		struct zstty_softc *zst1 = zsc->zsc_cs[1]->cs_private;
		if (zst0->zst_overflows || zst1->zst_overflows ) {
			struct trapframe *frame = (struct trapframe *)arg;

			printf("zs silo overflow from %p\n",
			       (long)frame->tf_pc);
		}
	}
#endif
	splx(s);
}

/*
 * Compute the current baud rate given a ZS channel.
 */
static int
zs_get_speed(cs)
	struct zs_chanstate *cs;
{
	int tconst;

	tconst = zs_read_reg(cs, 12);
	tconst |= zs_read_reg(cs, 13) << 8;
	return (TCONST_TO_BPS(cs->cs_brg_clk, tconst));
}

/*
 * MD functions for setting the baud rate and control modes.
 */
int
zs_set_speed(cs, bps)
	struct zs_chanstate *cs;
	int bps;	/* bits per second */
{
	int tconst, real_bps;

	if (bps == 0)
		return (0);

#ifdef	DIAGNOSTIC
	if (cs->cs_brg_clk == 0)
		panic("zs_set_speed");
#endif

	tconst = BPS_TO_TCONST(cs->cs_brg_clk, bps);
	if (tconst < 0)
		return (EINVAL);

	/* Convert back to make sure we can do it. */
	real_bps = TCONST_TO_BPS(cs->cs_brg_clk, tconst);

	/* XXX - Allow some tolerance here? */
	if (real_bps != bps)
		return (EINVAL);

	cs->cs_preg[12] = tconst;
	cs->cs_preg[13] = tconst >> 8;

	/* Caller will stuff the pending registers. */
	return (0);
}

int
zs_set_modes(cs, cflag)
	struct zs_chanstate *cs;
	int cflag;
{
	int s;

	/*
	 * Output hardware flow control on the chip is horrendous:
	 * if carrier detect drops, the receiver is disabled, and if
	 * CTS drops, the transmitter is stopped IN MID CHARACTER!
	 * Therefore, NEVER set the HFC bit, and instead use the
	 * status interrupt to detect CTS changes.
	 */
	s = splzs();
	cs->cs_rr0_pps = 0;
	if ((cflag & (CLOCAL | MDMBUF)) != 0) {
		cs->cs_rr0_dcd = 0;
		if ((cflag & MDMBUF) == 0)
			cs->cs_rr0_pps = ZSRR0_DCD;
	} else
		cs->cs_rr0_dcd = ZSRR0_DCD;
	if ((cflag & CRTSCTS) != 0) {
		cs->cs_wr5_dtr = ZSWR5_DTR;
		cs->cs_wr5_rts = ZSWR5_RTS;
		cs->cs_rr0_cts = ZSRR0_CTS;
#if 0 /* JLW */
	} else if ((cflag & CDTRCTS) != 0) {
		cs->cs_wr5_dtr = 0;
		cs->cs_wr5_rts = ZSWR5_DTR;
		cs->cs_rr0_cts = ZSRR0_CTS;
#endif
	} else if ((cflag & MDMBUF) != 0) {
		cs->cs_wr5_dtr = 0;
		cs->cs_wr5_rts = ZSWR5_DTR;
		cs->cs_rr0_cts = ZSRR0_DCD;
	} else {
		cs->cs_wr5_dtr = ZSWR5_DTR | ZSWR5_RTS;
		cs->cs_wr5_rts = 0;
		cs->cs_rr0_cts = 0;
	}
	splx(s);

	/* Caller will stuff the pending registers. */
	return (0);
}

/*
 * Read or write the chip with suitable delays.
 */

u_char
zs_read_reg(cs, reg)
	struct zs_chanstate *cs;
	u_char reg;
{
	u_char val;

	*cs->cs_reg_csr = reg;
	ZS_DELAY();
	val = *cs->cs_reg_csr;
	ZS_DELAY();
	return (val);
}

void
zs_write_reg(cs, reg, val)
	struct zs_chanstate *cs;
	u_char reg, val;
{
	*cs->cs_reg_csr = reg;
	ZS_DELAY();
	*cs->cs_reg_csr = val;
	ZS_DELAY();
}

u_char
zs_read_csr(cs)
	struct zs_chanstate *cs;
{
	u_char val;

	val = *cs->cs_reg_csr;
	ZS_DELAY();
	return (val);
}

void
zs_write_csr(cs, val)
	struct zs_chanstate *cs;
	u_char val;
{
	*cs->cs_reg_csr = val;
	ZS_DELAY();
}

u_char
zs_read_data(cs)
	struct zs_chanstate *cs;
{
	u_char val;

	val = *cs->cs_reg_data;
	ZS_DELAY();
	return (val);
}

void
zs_write_data(cs, val)
	struct zs_chanstate *cs;
	u_char val;
{
	*cs->cs_reg_data = val;
	ZS_DELAY();
}

/****************************************************************
 * Console support functions (Sun specific!)
 * Note: this code is allowed to know about the layout of
 * the chip registers, and uses that to keep things simple.
 * XXX - I think I like the mvme167 code better. -gwr
 ****************************************************************/

extern void Debugger(void);

/*
 * Handle user request to enter kernel debugger.
 */
void
zs_abort(cs)
	struct zs_chanstate *cs;
{
	volatile struct zschan *zc = zs_conschan_get;
	int rr0;

	/* Wait for end of break to avoid PROM abort. */
	/* XXX - Limit the wait? */
	do {
		rr0 = zc->zc_csr;
		ZS_DELAY();
	} while (rr0 & ZSRR0_BREAK);

#if defined(KGDB)
	zskgdb(cs);
#elif defined(DDB)
	{
		extern int db_active;

		if (!db_active)
			Debugger();
		else
			/* Debugger is probably hozed */
			callrom();
	}
#else
	printf("stopping on keyboard abort\n");
	callrom();
#endif
}

/*
 * Polled input char.
 */
int
zs_getc(arg)
	void *arg;
{
	volatile struct zschan *zc = arg;
	int s, c, rr0;

	s = splhigh();
	/* Wait for a character to arrive. */
	do {
		rr0 = zc->zc_csr;
		ZS_DELAY();
	} while ((rr0 & ZSRR0_RX_READY) == 0);

	c = zc->zc_data;
	ZS_DELAY();
	splx(s);

	return (c);
}

/*
 * Polled output char.
 */
void
zs_putc(arg, c)
	void *arg;
	int c;
{
	volatile struct zschan *zc = arg;
	int s, rr0;

	s = splhigh();

	/* Wait for transmitter to become ready. */
	do {
		rr0 = zc->zc_csr;
		ZS_DELAY();
	} while ((rr0 & ZSRR0_TX_READY) == 0);

	/*
	 * Send the next character.
	 * Now you'd think that this could be followed by a ZS_DELAY()
	 * just like all the other chip accesses, but it turns out that
	 * the `transmit-ready' interrupt isn't de-asserted until
	 * some period of time after the register write completes
	 * (more than a couple instructions).  So to avoid stray
	 * interrupts we put in the 2us delay regardless of cpu model.
	 */
	zc->zc_data = c;
	delay(2);

	splx(s);
}

/*****************************************************************/

/*
 * Polled console input putchar.
 */
static int
zscngetc(dev)
	dev_t dev;
{
	return (zs_getc(zs_conschan_get));
}

/*
 * Polled console output putchar.
 */
static void
zscnputc(dev, c)
	dev_t dev;
	int c;
{
	zs_putc(zs_conschan_put, c);
}

int swallow_zsintrs;

static void
zscnpollc(dev, on)
	dev_t dev;
	int on;
{
	/*
	 * Need to tell zs driver to acknowledge all interrupts or we get
	 * annoying spurious interrupt messages.  This is because mucking
	 * with spl() levels during polling does not prevent interrupts from
	 * being generated.
	 */

	if (on)
		swallow_zsintrs++;
	else
		swallow_zsintrs--;
}

int
zs_console_flags(promunit, node, channel)
	int promunit;
	int node;
	int channel;
{
	int cookie, flags = 0;
	u_int options;
	char buf[255];

	/*
	 * We'll just to the OBP grovelling down here since that's
	 * the only type of firmware we support.
	 */
	options = OF_finddevice("/options");

	/* Default to channel 0 if there are no explicit prom args */
	cookie = 0;

	if (node == OF_instance_to_package(OF_stdin())) {
		if (OF_getprop(options, "input-device",
		    buf, sizeof(buf)) != -1) {
			if (strncmp("ttyb", buf, strlen("ttyb")) == 0)
				cookie = 1;
		}

		if (channel == cookie)
			flags |= ZS_HWFLAG_CONSOLE_INPUT;
	}

	if (node == OF_instance_to_package(OF_stdout())) {
		if (OF_getprop(options, "output-device",
		    buf, sizeof(buf)) != -1) {
			if (strncmp("ttyb", buf, strlen("ttyb")) == 0)
				cookie = 1;
		}

		if (channel == cookie)
			flags |= ZS_HWFLAG_CONSOLE_OUTPUT;
	}

	return (flags);
}


code:
/*	$OpenBSD: fpu.c,v 1.18 2013/11/26 20:33:15 deraadt Exp $	*/
/*	$NetBSD: fpu.c,v 1.11 2000/12/06 01:47:50 mrg Exp $ */

/*
 * Copyright (c) 2001 Jason L. Wright (jason@thought.net)
 * 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 ``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 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.
 */

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *	@(#)fpu.c	8.1 (Berkeley) 6/11/93
 */

#include <sys/param.h>
#include <sys/proc.h>
#include <sys/signal.h>
#include <sys/systm.h>
#include <sys/syslog.h>
#include <sys/signalvar.h>

#include <machine/instr.h>
#include <machine/reg.h>

#include <sparc64/fpu/fpu_emu.h>
#include <sparc64/fpu/fpu_extern.h>

int fpu_regoffset(int, int);
int fpu_insn_fmov(struct fpstate64 *, struct fpemu *, union instr);
int fpu_insn_fabs(struct fpstate64 *, struct fpemu *, union instr);
int fpu_insn_fneg(struct fpstate64 *, struct fpemu *, union instr);
int fpu_insn_itof(struct fpemu *, union instr, int, int *,
    int *, u_int *);
int fpu_insn_ftoi(struct fpemu *, union instr, int *, int, u_int *);
int fpu_insn_ftof(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fsqrt(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fcmp(struct fpstate64 *, struct fpemu *, union instr, int);
int fpu_insn_fmul(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fmulx(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fdiv(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fadd(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fsub(struct fpemu *, union instr, int *, int *, u_int *);
int fpu_insn_fmovcc(struct proc *, struct fpstate64 *, union instr);
int fpu_insn_fmovr(struct proc *, struct fpstate64 *, union instr);
void fpu_fcopy(u_int *, u_int *, int);

#ifdef DEBUG
int fpe_debug = 0;

/*
 * Dump a `fpn' structure.
 */
void
fpu_dumpfpn(struct fpn *fp)
{
	static char *class[] = { "SNAN", "QNAN", "ZERO", "NUM", "INF" };

	printf("%s %c.%x %x %x %xE%d", class[fp->fp_class + 2],
	    fp->fp_sign ? '-' : ' ', fp->fp_mant[0], fp->fp_mant[1],
	    fp->fp_mant[2], fp->fp_mant[3], fp->fp_exp);
}
void
fpu_dumpstate(struct fpstate64 *fs)
{
	int i;

	for (i = 0; i < 64; i++)
		printf("%%f%02d: %08x%s",
		    i, fs->fs_regs[i], ((i & 3) == 3) ? "\n" : "   ");
}
#endif

/*
 * fpu_execute returns the following error numbers (0 = no error):
 */
#define	FPE		1	/* take a floating point exception */
#define	NOTFPU		2	/* not an FPU instruction */

/*
 * Translate current exceptions into `first' exception.  The
 * bits go the wrong way for ffs() (0x10 is most important, etc).
 * There are only 5, so do it the obvious way.
 */
#define	X1(x) x
#define	X2(x) x,x
#define	X4(x) x,x,x,x
#define	X8(x) X4(x),X4(x)
#define	X16(x) X8(x),X8(x)

static char cx_to_trapx[] = {
	X1(FSR_NX),
	X2(FSR_DZ),
	X4(FSR_UF),
	X8(FSR_OF),
	X16(FSR_NV)
};
static u_char fpu_codes[] = {
	X1(FPE_FLTINEX_TRAP),
	X2(FPE_FLTDIV_TRAP),
	X4(FPE_FLTUND_TRAP),
	X8(FPE_FLTOVF_TRAP),
	X16(FPE_FLTOPERR_TRAP)
};

static int fpu_types[] = {
	X1(FPE_FLTRES),
	X2(FPE_FLTDIV),
	X4(FPE_FLTUND),
	X8(FPE_FLTOVF),
	X16(FPE_FLTINV)
};

void
fpu_fcopy(src, dst, type)
	u_int *src, *dst;
	int type;
{
	*dst++ = *src++;
	if (type == FTYPE_SNG || type == FTYPE_INT)
		return;
	*dst++ = *src++;
	if (type != FTYPE_EXT)
		return;
	*dst++ = *src++;
	*dst = *src;
}

/*
 * The FPU gave us an exception.  Clean up the mess.  Note that the
 * fp queue can only have FPops in it, never load/store FP registers
 * nor FBfcc instructions.  Experiments with `crashme' prove that
 * unknown FPops do enter the queue, however.
 */
void
fpu_cleanup(p, fs)
	register struct proc *p;
	register struct fpstate64 *fs;
{
	register int i, fsr = fs->fs_fsr, error;
	union instr instr;
	union sigval sv;
	struct fpemu fe;

	sv.sival_int = p->p_md.md_tf->tf_pc;  /* XXX only approximate */

	switch ((fsr >> FSR_FTT_SHIFT) & FSR_FTT_MASK) {

	case FSR_TT_NONE:
#if 0
		/* XXX I'm not sure how we get here, but ignoring the trap */
		/* XXX seems to work in my limited tests		   */
		/* XXX More research to be done =)			   */
		panic("fpu_cleanup 1"); /* ??? */
#else
		printf("fpu_cleanup 1\n");
#endif
		break;

	case FSR_TT_IEEE:
		if ((i = fsr & FSR_CX) == 0)
			panic("fpu ieee trap, but no exception");
		KERNEL_LOCK();
		trapsignal(p, SIGFPE, fpu_codes[i - 1], fpu_types[i - 1], sv);
		KERNEL_UNLOCK();
		break;		/* XXX should return, but queue remains */

	case FSR_TT_UNFIN:
		if (fs->fs_qsize == 0) {
			printf("fpu_cleanup: unfinished fpop");
			/* The book says reexecute or emulate. */
			return;
		}
		break;
	case FSR_TT_UNIMP:
		if (fs->fs_qsize == 0)
			panic("fpu_cleanup: unimplemented fpop");
		break;

	case FSR_TT_SEQ:
		panic("fpu sequence error");
		/* NOTREACHED */

	case FSR_TT_HWERR:
		log(LOG_ERR, "fpu hardware error (%s[%d])\n",
		    p->p_comm, p->p_pid);
		uprintf("%s[%d]: fpu hardware error\n", p->p_comm, p->p_pid);
		KERNEL_LOCK();
		trapsignal(p, SIGFPE, -1, FPE_FLTINV, sv);	/* ??? */
		KERNEL_UNLOCK();
		goto out;

	default:
		printf("fsr=0x%x\n", fsr);
		panic("fpu error");
	}

	/* emulate the instructions left in the queue */
	fe.fe_fpstate = fs;
	for (i = 0; i < fs->fs_qsize; i++) {
		instr.i_int = fs->fs_queue[i].fq_instr;
		if (instr.i_any.i_op != IOP_reg ||
		    (instr.i_op3.i_op3 != IOP3_FPop1 &&
		     instr.i_op3.i_op3 != IOP3_FPop2))
			panic("bogus fpu queue");
		error = fpu_execute(p, &fe, instr);
		switch (error) {

		case 0:
			continue;

		case FPE:
			KERNEL_LOCK();
			trapsignal(p, SIGFPE,
			    fpu_codes[(fs->fs_fsr & FSR_CX) - 1],
			    fpu_types[(fs->fs_fsr & FSR_CX) - 1], sv);
			KERNEL_UNLOCK();
			break;

		case NOTFPU:
			KERNEL_LOCK();
			trapsignal(p, SIGILL, 0, ILL_COPROC, sv);
			KERNEL_UNLOCK();
			break;

		default:
			panic("fpu_cleanup 3");
			/* NOTREACHED */
		}
		/* XXX should stop here, but queue remains */
	}
out:
	fs->fs_qsize = 0;
}

/*
 * Compute offset given a register and type.  For 32 bit sparc, bits 1 and 0
 * must be zero for ext types, and bit 0 must be 0 for double and long types.
 * For 64bit sparc, bit 1 must be zero for quad types, and bit 0 becomes bit
 * 5 in the register offset for long, double, and quad types.
 */
int
fpu_regoffset(rx, type)
	int rx, type;
{
	if (type == FTYPE_LNG || type == FTYPE_DBL || type == FTYPE_EXT) {
		rx |= (rx & 1) << 5;
		rx &= 0x3e;
		if ((type == FTYPE_EXT) && (rx & 2))
			return (-1);
	}
	return (rx);
}

/*
 * Execute an FPU instruction (one that runs entirely in the FPU; not
 * FBfcc or STF, for instance).  On return, fe->fe_fs->fs_fsr will be
 * modified to reflect the setting the hardware would have left.
 */
int
fpu_execute(p, fe, instr)
	struct proc *p;
	struct fpemu *fe;
	union instr instr;
{
	struct fpstate *fs;
	int opf, rdtype, rd, err, mask, cx, fsr;
	u_int space[4];

	DPRINTF(FPE_INSN, ("op3: %x, opf %x\n", instr.i_opf.i_op3,
	    instr.i_opf.i_opf));
	DPRINTF(FPE_STATE, ("BEFORE:\n"));
	DUMPSTATE(FPE_STATE, fe->fe_fpstate);
	opf = instr.i_opf.i_opf;
	fs = fe->fe_fpstate;
	fe->fe_fsr = fs->fs_fsr & ~FSR_CX;
	fe->fe_cx = 0;

	if ((instr.i_int & 0xc0000000) != 0x80000000)
		return (NOTFPU);

	if (instr.i_opf.i_op3 == IOP3_FPop2) {
		switch (opf) {
		case FCMPS: case FCMPD: case FCMPQ:
			return (fpu_insn_fcmp(fs, fe, instr, 0));

		case FCMPES: case FCMPED: case FCMPEQ:
			return (fpu_insn_fcmp(fs, fe, instr, 1));

		case FMVFC0S: case FMVFC0D: case FMVFC0Q:
		case FMVFC1S: case FMVFC1D: case FMVFC1Q:
		case FMVFC2S: case FMVFC2D: case FMVFC2Q:
		case FMVFC3S: case FMVFC3D: case FMVFC3Q:
		case FMVICS: case FMVICD: case FMVICQ:
		case FMVXCS: case FMVXCD: case FMVXCQ:
			return (fpu_insn_fmovcc(p, fs, instr));

		case FMOVZS: case FMOVZD: case FMOVZQ:
		case FMOVLEZS: case FMOVLEZD: case FMOVLEZQ:
		case FMOVLZS: case FMOVLZD: case FMOVLZQ:
		case FMOVNZS: case FMOVNZD: case FMOVNZQ:
		case FMOVGZS: case FMOVGZD: case FMOVGZQ:
		case FMOVGEZS: case FMOVGEZD: case FMOVGEZQ:
			return (fpu_insn_fmovr(p, fs, instr));
		}
		return (NOTFPU);
	}

	if (instr.i_opf.i_op3 != IOP3_FPop1)
		return (NOTFPU);

	switch (instr.i_opf.i_opf) {
	case FSTOX: case FDTOX: case FQTOX:
		rdtype = FTYPE_LNG;
		if ((err = fpu_insn_ftoi(fe, instr, &rd, rdtype, space)) != 0)
			return (err);
		break;

	case FSTOI: case FDTOI: case FQTOI:
		rdtype = FTYPE_INT;
		if ((err = fpu_insn_ftoi(fe, instr, &rd, rdtype, space)) != 0)
			return (err);
		break;

	case FITOS: case FITOD: case FITOQ:
		if ((err = fpu_insn_itof(fe, instr, FTYPE_INT, &rd,
		    &rdtype, space)) != 0)
			return (err);
		break;

	case FXTOS: case FXTOD: case FXTOQ:
		if ((err = fpu_insn_itof(fe, instr, FTYPE_LNG, &rd,
		    &rdtype, space)) != 0)
			return (err);
		break;

	case FSTOD: case FSTOQ:
	case FDTOS: case FDTOQ:
	case FQTOS: case FQTOD:
		if ((err = fpu_insn_ftof(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FMOVS: case FMOVD: case FMOVQ:
		return (fpu_insn_fmov(fs, fe, instr));

	case FNEGS: case FNEGD: case FNEGQ:
		return (fpu_insn_fneg(fs, fe, instr));

	case FABSS: case FABSD: case FABSQ:
		return (fpu_insn_fabs(fs, fe, instr));

	case FSQRTS: case FSQRTD: case FSQRTQ:
		if ((err = fpu_insn_fsqrt(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FMULS: case FMULD: case FMULQ:
		if ((err = fpu_insn_fmul(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FDIVS: case FDIVD: case FDIVQ:
		if ((err = fpu_insn_fdiv(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FSMULD: case FDMULQ:
		if ((err = fpu_insn_fmulx(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FADDS: case FADDD: case FADDQ:
		if ((err = fpu_insn_fadd(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;

	case FSUBS: case FSUBD: case FSUBQ:
		if ((err = fpu_insn_fsub(fe, instr, &rd, &rdtype, space)) != 0)
			return (err);
		break;
	default:
		return (NOTFPU);
	}

	cx = fe->fe_cx;
	fsr = fe->fe_fsr;
	if (cx != 0) {
		mask = (fsr >> FSR_TEM_SHIFT) & FSR_TEM_MASK;
		if (cx & mask) {
			/* not accrued??? */
			fs->fs_fsr = (fsr & ~FSR_FTT) |
			    (FSR_TT_IEEE << FSR_FTT_SHIFT) |
			    (cx_to_trapx[(cx & mask) - 1] << FSR_CX_SHIFT);
			return (FPE);
		}
		fsr |= (cx << FSR_CX_SHIFT) | (cx << FSR_AX_SHIFT);
	}
	fs->fs_fsr = fsr;
	fpu_fcopy(space, fs->fs_regs + rd, rdtype);
	DPRINTF(FPE_STATE, ("AFTER:\n"));
	DUMPSTATE(FPE_STATE, fs);
	return (0);
}

/*
 * Handler for FMOV[SDQ] emulation.
 */
int
fpu_insn_fmov(fs, fe, instr)
	struct fpstate64 *fs;
	struct fpemu *fe;
	union instr instr;
{
	int opf = instr.i_opf.i_opf, rs, rd, rtype;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);
	fpu_fcopy(fs->fs_regs + rs, fs->fs_regs + rd, rtype);
	fs->fs_fsr = fe->fe_fsr;
	return (0);
}

/*
 * Handler for FABS[SDQ] emulation.
 */
int
fpu_insn_fabs(fs, fe, instr)
	struct fpstate64 *fs;
	struct fpemu *fe;
	union instr instr;
{
	int opf = instr.i_opf.i_opf, rs, rd, rtype;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);
	fpu_fcopy(fs->fs_regs + rs, fs->fs_regs + rd, rtype);
	fs->fs_regs[rd] = fs->fs_regs[rd] & ~(1U << 31);
	fs->fs_fsr = fe->fe_fsr;
	return (0);
}

/*
 * Handler for FNEG[SDQ] emulation.
 */
int
fpu_insn_fneg(fs, fe, instr)
	struct fpstate64 *fs;
	struct fpemu *fe;
	union instr instr;
{
	int opf = instr.i_opf.i_opf, rs, rd, rtype;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);
	fpu_fcopy(fs->fs_regs + rs, fs->fs_regs + rd, rtype);
	fs->fs_regs[rd] = fs->fs_regs[rd] ^ (1U << 31);
	fs->fs_fsr = fe->fe_fsr;
	return (0);
}

/*
 * Handler for F[XI]TO[SDQ] emulation.
 */
int
fpu_insn_itof(fe, instr, rstype, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	u_int *space;
	int rstype, *rdp, *rdtypep;
{
	int opf = instr.i_opf.i_opf, rs, rd, rdtype;

	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rstype)) < 0)
		return (NOTFPU);

	rdtype = (opf >> 2) & 3;
	if (rdtype == 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rdtype)) < 0)
		return (NOTFPU);

	DPRINTF(FPE_INSN, ("itof %%f%d(%d, %d) -> %%f%d(%d, %d)\n",
	    rs, rstype, instr.i_opf.i_rs2, rd, rdtype, instr.i_opf.i_rd));
	fpu_explode(fe, &fe->fe_f1, rstype, rs);
	fpu_implode(fe, &fe->fe_f1, rdtype, space);
	*rdp = rd;
	*rdtypep = rdtype;
	return (0);
}

/*
 * Handler for F[SDQ]TO[XI] emulation.
 */
int
fpu_insn_ftoi(fe, instr, rdp, rdtype, space)
	struct fpemu *fe;
	union instr instr;
	u_int *space;
	int *rdp, rdtype;
{
	int opf = instr.i_opf.i_opf, rd, rstype, rs;

	rstype = opf & 3;
	if (rstype == 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rstype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rdtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rstype, rs);
	fpu_implode(fe, &fe->fe_f1, rdtype, space);
	*rdp = rd;
	return (0);
}

/*
 * Handler for F[SDQ]TO[SDQ] emulation.
 */
int
fpu_insn_ftof(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	u_int *space;
	int *rdp, *rdtypep;
{
	int opf = instr.i_opf.i_opf, rd, rs, rdtype, rstype;

	rstype = opf & 3;
	rdtype = (opf >> 2) & 3;

	if ((rstype == rdtype) || (rstype == 0) || (rdtype == 0))
		return (NOTFPU);

	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rstype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rdtype)) < 0)
		return (NOTFPU);

	DPRINTF(FPE_INSN, ("ftof %%f%d(%d, %d) -> %%f%d(%d, %d)\n",
	    rs, rstype, instr.i_opf.i_rs2, rd, rdtype, instr.i_opf.i_rd));

	fpu_explode(fe, &fe->fe_f1, rstype, rs);
	fpu_implode(fe, &fe->fe_f1, rdtype, space);
	*rdp = rd;
	*rdtypep = rdtype;
	return (0);
}

/*
 * Handler for FQSRT[SDQ] emulation.
 */
int
fpu_insn_fsqrt(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	u_int *space;
	int *rdp, *rdtypep;
{
	int opf = instr.i_opf.i_opf, rd, rs, rtype;
	struct fpn *fp;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs);
	fp = fpu_sqrt(fe);
	fpu_implode(fe, fp, rtype, space);
	*rdp = rd;
	*rdtypep = rtype;
	return (0);
}

/*
 * Handler for FCMP{E}[SDQ] emulation.
 */
int
fpu_insn_fcmp(fs, fe, instr, cmpe)
	struct fpstate64 *fs;
	struct fpemu *fe;
	union instr instr;
	int cmpe;
{
	int opf = instr.i_opf.i_opf, rs1, rs2, rtype, cx, fsr;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs1);
	fpu_explode(fe, &fe->fe_f2, rtype, rs2);
	fpu_compare(fe, cmpe);

	/*
	 * The only possible exception here is NV; catch it early
	 * and get out, as there is no result register.
	 */
	cx = fe->fe_cx;
	fsr = fe->fe_fsr | (cx << FSR_CX_SHIFT);
	if (cx != 0) {
		if (fsr & (FSR_NV << FSR_TEM_SHIFT)) {
			fs->fs_fsr = (fsr & ~FSR_FTT) |
			    (FSR_TT_IEEE << FSR_FTT_SHIFT);
			return (FPE);
		}
		fsr |= FSR_NV << FSR_AX_SHIFT;
	}
	fs->fs_fsr = fsr;
	return (0);
}

/*
 * Handler for FMUL[SDQ] emulation.
 */
int
fpu_insn_fmul(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	int *rdp, *rdtypep;
	u_int *space;
{
	struct fpn *fp;
	int opf = instr.i_opf.i_opf, rd, rtype, rs1, rs2;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs1);
	fpu_explode(fe, &fe->fe_f2, rtype, rs2);
	fp = fpu_mul(fe);
	fpu_implode(fe, fp, rtype, space);
	*rdp = rd;
	*rdtypep = rtype;
	return (0);
}

/*
 * Handler for FSMULD, FDMULQ emulation.
 */
int
fpu_insn_fmulx(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	int *rdp, *rdtypep;
	u_int *space;
{
	struct fpn *fp;
	int opf = instr.i_opf.i_opf, rd, rdtype, rstype, rs1, rs2;

	rstype = opf & 3;
	rdtype = (opf >> 2) & 3;
	if ((rstype != rdtype + 1) || (rstype == 0) || (rdtype == 0))
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rstype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rstype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rdtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rstype, rs1);
	fpu_explode(fe, &fe->fe_f2, rstype, rs2);
	fp = fpu_mul(fe);
	fpu_implode(fe, fp, rdtype, space);
	*rdp = rd;
	*rdtypep = rdtype;
	return (0);
}

/*
 * Handler for FDIV[SDQ] emulation.
 */
int
fpu_insn_fdiv(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	int *rdp, *rdtypep;
	u_int *space;
{
	struct fpn *fp;
	int opf = instr.i_opf.i_opf, rd, rtype, rs1, rs2;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs1);
	fpu_explode(fe, &fe->fe_f2, rtype, rs2);
	fp = fpu_div(fe);
	fpu_implode(fe, fp, rtype, space);
	*rdp = rd;
	*rdtypep = rtype;
	return (0);
}

/*
 * Handler for FADD[SDQ] emulation.
 */
int
fpu_insn_fadd(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	int *rdp, *rdtypep;
	u_int *space;
{
	struct fpn *fp;
	int opf = instr.i_opf.i_opf, rd, rtype, rs1, rs2;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs1);
	fpu_explode(fe, &fe->fe_f2, rtype, rs2);
	fp = fpu_add(fe);
	fpu_implode(fe, fp, rtype, space);
	*rdp = rd;
	*rdtypep = rtype;
	return (0);
}

/*
 * Handler for FSUB[SDQ] emulation.
 */
int
fpu_insn_fsub(fe, instr, rdp, rdtypep, space)
	struct fpemu *fe;
	union instr instr;
	int *rdp, *rdtypep;
	u_int *space;
{
	struct fpn *fp;
	int opf = instr.i_opf.i_opf, rd, rtype, rs1, rs2;

	rtype = opf & 3;
	if (rtype == 0)
		return (NOTFPU);
	if ((rs1 = fpu_regoffset(instr.i_opf.i_rs1, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_opf.i_rs2, rtype)) < 0)
		return (NOTFPU);
	if ((rd = fpu_regoffset(instr.i_opf.i_rd, rtype)) < 0)
		return (NOTFPU);

	fpu_explode(fe, &fe->fe_f1, rtype, rs1);
	fpu_explode(fe, &fe->fe_f2, rtype, rs2);
	fp = fpu_sub(fe);
	fpu_implode(fe, fp, rtype, space);
	*rdp = rd;
	*rdtypep = rtype;
	return (0);
}

/*
 * Handler for FMOV[SDQ][cond] emulation.
 */
int
fpu_insn_fmovcc(p, fs, instr)
	struct proc *p;
	struct fpstate64 *fs;
	union instr instr;
{
	int rtype, rd, rs, cond;

	rtype = instr.i_fmovcc.i_opf_low & 3;
	if ((rtype == 0) || (instr.i_int & 0x00040000))
		return (NOTFPU);

	if ((rd = fpu_regoffset(instr.i_fmovcc.i_rd, rtype)) < 0)
		return (NOTFPU);
	if ((rs = fpu_regoffset(instr.i_fmovcc.i_rs2, rtype)) < 0)
		return (NOTFPU);

	switch (instr.i_fmovcc.i_opf_cc) {
	case 0:
		cond = (fs->fs_fsr >> FSR_FCC_SHIFT) & FSR_FCC_MASK;
		break;
	case 1:
		cond = (fs->fs_fsr >> FSR_FCC1_SHIFT) & FSR_FCC_MASK;
		break;
	case 2:
		cond = (fs->fs_fsr >> FSR_FCC2_SHIFT) & FSR_FCC_MASK;
		break;
	case 3:
		cond = (fs->fs_fsr >> FSR_FCC3_SHIFT) & FSR_FCC_MASK;
		break;
	case 4:
		cond = (p->p_md.md_tf->tf_tstate >> TSTATE_CCR_SHIFT) &
		    PSR_ICC;
		break;
	case 6:
		cond = (p->p_md.md_tf->tf_tstate >>
		    (TSTATE_CCR_SHIFT + XCC_SHIFT)) & PSR_ICC;
		break;
	default:
		return (NOTFPU);
	}

	if (instr.i_fmovcc.i_cond != cond)
		return (0);

	fpu_fcopy(fs->fs_regs + rs, fs->fs_regs + rd, rtype);
	return (0);
}

/*
 * Handler for FMOVR[icond][SDQ] emulation.
 */
int
fpu_insn_fmovr(p, fs, instr)
	struct proc *p;
	struct fpstate64 *fs;
	union instr instr;
{
	int rtype, rd, rs2, rs1;

	rtype = instr.i_fmovcc.i_opf_low & 3;
	if ((rtype == 0) || (instr.i_int & 0x00002000))
		return (NOTFPU);

	if ((rd = fpu_regoffset(instr.i_fmovr.i_rd, rtype)) < 0)
		return (NOTFPU);
	if ((rs2 = fpu_regoffset(instr.i_fmovr.i_rs2, rtype)) < 0)
		return (NOTFPU);
	rs1 = instr.i_fmovr.i_rs1;

	switch (instr.i_fmovr.i_rcond) {
	case 1:	/* Z */
		if (rs1 != 0 &&
		    (int64_t)p->p_md.md_tf->tf_global[rs1] != 0)
			return (0);
		break;
	case 2: /* LEZ */
		if (rs1 != 0 &&
		    (int64_t)p->p_md.md_tf->tf_global[rs1] > 0)
			return (0);
		break;
	case 3: /* LZ */
		if (rs1 == 0 ||
		    (int64_t)p->p_md.md_tf->tf_global[rs1] >= 0)
			return (0);
		break;
	case 5:	/* NZ */
		if (rs1 == 0 ||
		    (int64_t)p->p_md.md_tf->tf_global[rs1] == 0)
			return (0);
		break;
	case 6: /* NGZ */
		if (rs1 == 0 ||
		    (int64_t)p->p_md.md_tf->tf_global[rs1] <= 0)
			return (0);
		break;
	case 7: /* NGEZ */
		if (rs1 != 0 &&
		    (int64_t)p->p_md.md_tf->tf_global[rs1] < 0)
			return (0);
		break;
	default:
		return (NOTFPU);
	}

	fpu_fcopy(fs->fs_regs + rs2, fs->fs_regs + rd, rtype);
	return (0);
}