code:
/*	$OpenBSD: if_cnmac.c,v 1.23 2015/06/11 12:30:42 jmatthew Exp $	*/

/*
 * Copyright (c) 2007 Internet Initiative Japan, Inc.
 * 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 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.
 */
#include "bpfilter.h"

/*
 * XXXSEIL
 * If no free send buffer is available, free all the sent buffer and bail out.
 */
#define OCTEON_ETH_SEND_QUEUE_CHECK

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/queue.h>
#include <sys/conf.h>
#include <sys/stdint.h> /* uintptr_t */
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/endian.h>
#ifdef MBUF_TIMESTAMP
#include <sys/time.h>
#endif

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/octeonvar.h>
#include <machine/octeon_model.h>

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>

#include <octeon/dev/cn30xxasxreg.h>
#include <octeon/dev/cn30xxciureg.h>
#include <octeon/dev/cn30xxnpireg.h>
#include <octeon/dev/cn30xxgmxreg.h>
#include <octeon/dev/cn30xxipdreg.h>
#include <octeon/dev/cn30xxpipreg.h>
#include <octeon/dev/cn30xxpowreg.h>
#include <octeon/dev/cn30xxfaureg.h>
#include <octeon/dev/cn30xxfpareg.h>
#include <octeon/dev/cn30xxbootbusreg.h>
#include <octeon/dev/cn30xxfpavar.h>
#include <octeon/dev/cn30xxgmxvar.h>
#include <octeon/dev/cn30xxfauvar.h>
#include <octeon/dev/cn30xxpowvar.h>
#include <octeon/dev/cn30xxipdvar.h>
#include <octeon/dev/cn30xxpipvar.h>
#include <octeon/dev/cn30xxpkovar.h>
#include <octeon/dev/cn30xxasxvar.h>
#include <octeon/dev/cn30xxsmivar.h>
#include <octeon/dev/iobusvar.h>
#include <octeon/dev/if_cnmacvar.h>

#ifdef OCTEON_ETH_DEBUG
#define	OCTEON_ETH_KASSERT(x)	KASSERT(x)
#define	OCTEON_ETH_KDASSERT(x)	KDASSERT(x)
#else
#define	OCTEON_ETH_KASSERT(x)
#define	OCTEON_ETH_KDASSERT(x)
#endif

/*
 * Set the PKO to think command buffers are an odd length.  This makes it so we
 * never have to divide a comamnd across two buffers.
 */
#define OCTEON_POOL_NWORDS_CMD	\
	    (((uint32_t)OCTEON_POOL_SIZE_CMD / sizeof(uint64_t)) - 1)
#define FPA_COMMAND_BUFFER_POOL_NWORDS	OCTEON_POOL_NWORDS_CMD	/* XXX */

#if NBPFILTER > 0
#define	OCTEON_ETH_TAP(ifp, m, dir) \
	do { \
		/* Pass this up to any BPF listeners. */ \
		if ((ifp)->if_bpf) \
			bpf_mtap((ifp)->if_bpf, (m), (dir)); \
	} while (0/* CONSTCOND */)
#else
#define	OCTEON_ETH_TAP(ifp, m, dir)
#endif /* NBPFILTER > 0 */

void	octeon_eth_buf_init(struct octeon_eth_softc *);

int	octeon_eth_match(struct device *, void *, void *);
void	octeon_eth_attach(struct device *, struct device *, void *);
void	octeon_eth_pip_init(struct octeon_eth_softc *);
void	octeon_eth_ipd_init(struct octeon_eth_softc *);
void	octeon_eth_pko_init(struct octeon_eth_softc *);
void	octeon_eth_asx_init(struct octeon_eth_softc *);
void	octeon_eth_smi_init(struct octeon_eth_softc *);

void	octeon_eth_board_mac_addr(uint8_t *);

int	octeon_eth_mii_readreg(struct device *, int, int);
void	octeon_eth_mii_writereg(struct device *, int, int, int);
void	octeon_eth_mii_statchg(struct device *);

int	octeon_eth_mediainit(struct octeon_eth_softc *);
void	octeon_eth_mediastatus(struct ifnet *, struct ifmediareq *);
int	octeon_eth_mediachange(struct ifnet *);

void	octeon_eth_send_queue_flush_prefetch(struct octeon_eth_softc *);
void	octeon_eth_send_queue_flush_fetch(struct octeon_eth_softc *);
void	octeon_eth_send_queue_flush(struct octeon_eth_softc *);
void	octeon_eth_send_queue_flush_sync(struct octeon_eth_softc *);
int	octeon_eth_send_queue_is_full(struct octeon_eth_softc *);
void	octeon_eth_send_queue_add(struct octeon_eth_softc *,
	    struct mbuf *, uint64_t *);
void	octeon_eth_send_queue_del(struct octeon_eth_softc *,
	    struct mbuf **, uint64_t **);
int	octeon_eth_buf_free_work(struct octeon_eth_softc *,
	    uint64_t *, uint64_t);
void	octeon_eth_buf_ext_free_m(caddr_t, u_int, void *);
void	octeon_eth_buf_ext_free_ext(caddr_t, u_int, void *);

int	octeon_eth_ioctl(struct ifnet *, u_long, caddr_t);
void	octeon_eth_watchdog(struct ifnet *);
int	octeon_eth_init(struct ifnet *);
int	octeon_eth_stop(struct ifnet *, int);
void	octeon_eth_start(struct ifnet *);

int	octeon_eth_send_cmd(struct octeon_eth_softc *, uint64_t, uint64_t);
uint64_t octeon_eth_send_makecmd_w1(int, paddr_t);
uint64_t octeon_eth_send_makecmd_w0(uint64_t, uint64_t, size_t, int);
int	octeon_eth_send_makecmd_gbuf(struct octeon_eth_softc *,
	    struct mbuf *, uint64_t *, int *);
int	octeon_eth_send_makecmd(struct octeon_eth_softc *,
	    struct mbuf *, uint64_t *, uint64_t *, uint64_t *);
int	octeon_eth_send_buf(struct octeon_eth_softc *,
	    struct mbuf *, uint64_t *);
int	octeon_eth_send(struct octeon_eth_softc *, struct mbuf *);

int	octeon_eth_reset(struct octeon_eth_softc *);
int	octeon_eth_configure(struct octeon_eth_softc *);
int	octeon_eth_configure_common(struct octeon_eth_softc *);

void	octeon_eth_tick_free(void *arg);
void	octeon_eth_tick_misc(void *);

int	octeon_eth_recv_mbuf(struct octeon_eth_softc *,
	    uint64_t *, struct mbuf **);
int	octeon_eth_recv_check_code(struct octeon_eth_softc *, uint64_t);
#if 0 /* not used */
int      octeon_eth_recv_check_jumbo(struct octeon_eth_softc *, uint64_t);
#endif
int	octeon_eth_recv_check_link(struct octeon_eth_softc *, uint64_t);
int	octeon_eth_recv_check(struct octeon_eth_softc *, uint64_t);
int	octeon_eth_recv(struct octeon_eth_softc *, uint64_t *);
void	octeon_eth_recv_intr(void *, uint64_t *);

/* device driver context */
struct	octeon_eth_softc *octeon_eth_gsc[GMX_PORT_NUNITS];
void	*octeon_eth_pow_recv_ih;

/* sysctl'able parameters */
int	octeon_eth_param_pko_cmd_w0_n2 = 1;
int	octeon_eth_param_pip_dyn_rs = 1;
int	octeon_eth_param_redir = 0;
int	octeon_eth_param_pktbuf = 0;
int	octeon_eth_param_rate = 0;
int	octeon_eth_param_intr = 0;

const struct cfattach cnmac_ca =
    { sizeof(struct octeon_eth_softc), octeon_eth_match, octeon_eth_attach };

struct cfdriver cnmac_cd = { NULL, "cnmac", DV_IFNET };

/* ---- buffer management */

const struct octeon_eth_pool_param {
	int			poolno;
	size_t			size;
	size_t			nelems;
} octeon_eth_pool_params[] = {
#define	_ENTRY(x)	{ OCTEON_POOL_NO_##x, OCTEON_POOL_SIZE_##x, OCTEON_POOL_NELEMS_##x }
	_ENTRY(PKT),
	_ENTRY(WQE),
	_ENTRY(CMD),
	_ENTRY(SG)
#undef	_ENTRY
};
struct cn30xxfpa_buf	*octeon_eth_pools[8/* XXX */];
#define	octeon_eth_fb_pkt	octeon_eth_pools[OCTEON_POOL_NO_PKT]
#define	octeon_eth_fb_wqe	octeon_eth_pools[OCTEON_POOL_NO_WQE]
#define	octeon_eth_fb_cmd	octeon_eth_pools[OCTEON_POOL_NO_CMD]
#define	octeon_eth_fb_sg	octeon_eth_pools[OCTEON_POOL_NO_SG]

uint64_t octeon_eth_mac_addr = 0;
uint32_t octeon_eth_mac_addr_offset = 0;

void
octeon_eth_buf_init(struct octeon_eth_softc *sc)
{
	static int once;
	int i;
	const struct octeon_eth_pool_param *pp;
	struct cn30xxfpa_buf *fb;

	if (once == 1)
		return;
	once = 1;

	for (i = 0; i < (int)nitems(octeon_eth_pool_params); i++) {
		pp = &octeon_eth_pool_params[i];
		cn30xxfpa_buf_init(pp->poolno, pp->size, pp->nelems, &fb);
		octeon_eth_pools[i] = fb;
	}
}

/* ---- autoconf */

int
octeon_eth_match(struct device *parent, void *match, void *aux)
{
	struct cfdata *cf = (struct cfdata *)match;
	struct cn30xxgmx_attach_args *ga = aux;

	if (strcmp(cf->cf_driver->cd_name, ga->ga_name) != 0) {
		return 0;
	}
	return 1;
}

void
octeon_eth_attach(struct device *parent, struct device *self, void *aux)
{
	struct octeon_eth_softc *sc = (void *)self;
	struct cn30xxgmx_attach_args *ga = aux;
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	uint8_t enaddr[ETHER_ADDR_LEN];

	sc->sc_regt = ga->ga_regt;
	sc->sc_dmat = ga->ga_dmat;
	sc->sc_port = ga->ga_portno;
	sc->sc_port_type = ga->ga_port_type;
	sc->sc_gmx = ga->ga_gmx;
	sc->sc_gmx_port = ga->ga_gmx_port;
	sc->sc_phy_addr = ga->ga_phy_addr;

	sc->sc_init_flag = 0;

	/*
	 * XXX
	 * Setting PIP_IP_OFFSET[OFFSET] to 8 causes panic ... why???
	 */
	sc->sc_ip_offset = 0/* XXX */;

	octeon_eth_board_mac_addr(enaddr);
	printf(", address %s\n", ether_sprintf(enaddr));

	octeon_eth_gsc[sc->sc_port] = sc;

	ml_init(&sc->sc_sendq);
	sc->sc_soft_req_thresh = 15/* XXX */;
	sc->sc_ext_callback_cnt = 0;

	cn30xxgmx_stats_init(sc->sc_gmx_port);

	timeout_set(&sc->sc_tick_misc_ch, octeon_eth_tick_misc, sc);
	timeout_set(&sc->sc_tick_free_ch, octeon_eth_tick_free, sc);

	cn30xxfau_op_init(&sc->sc_fau_done,
	    OCTEON_CVMSEG_ETHER_OFFSET(sc->sc_port, csm_ether_fau_done),
	    OCT_FAU_REG_ADDR_END - (8 * (sc->sc_port + 1))/* XXX */);
	cn30xxfau_op_set_8(&sc->sc_fau_done, 0);

	octeon_eth_pip_init(sc);
	octeon_eth_ipd_init(sc);
	octeon_eth_pko_init(sc);
	octeon_eth_asx_init(sc);
	octeon_eth_smi_init(sc);

	sc->sc_gmx_port->sc_ipd = sc->sc_ipd;
	sc->sc_gmx_port->sc_port_asx = sc->sc_asx;
	sc->sc_gmx_port->sc_port_mii = &sc->sc_mii;
	sc->sc_gmx_port->sc_port_ac = &sc->sc_arpcom;

	/* XXX */
	sc->sc_pow = &cn30xxpow_softc;

	octeon_eth_mediainit(sc);

	strncpy(ifp->if_xname, sc->sc_dev.dv_xname, sizeof(ifp->if_xname));
	ifp->if_softc = sc;
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
	ifp->if_ioctl = octeon_eth_ioctl;
	ifp->if_start = octeon_eth_start;
	ifp->if_watchdog = octeon_eth_watchdog;
	IFQ_SET_MAXLEN(&ifp->if_snd, max(GATHER_QUEUE_SIZE, IFQ_MAXLEN));
	IFQ_SET_READY(&ifp->if_snd);

	ifp->if_capabilities = IFCAP_VLAN_MTU;

	cn30xxgmx_set_mac_addr(sc->sc_gmx_port, enaddr);
	cn30xxgmx_set_filter(sc->sc_gmx_port);

	if_attach(ifp);

	memcpy(sc->sc_arpcom.ac_enaddr, enaddr, ETHER_ADDR_LEN);
	ether_ifattach(ifp);

	/* XXX */
	sc->sc_rate_recv_check_link_cap.tv_sec = 1;
	sc->sc_rate_recv_check_jumbo_cap.tv_sec = 1;
	sc->sc_rate_recv_check_code_cap.tv_sec = 1;

#if 1
	octeon_eth_buf_init(sc);
#endif

	if (octeon_eth_pow_recv_ih == NULL)
		octeon_eth_pow_recv_ih = cn30xxpow_intr_establish(OCTEON_POW_GROUP_PIP,
		    IPL_NET, octeon_eth_recv_intr, NULL, NULL, sc->sc_dev.dv_xname);
}

/* ---- submodules */

/* XXX */
void
octeon_eth_pip_init(struct octeon_eth_softc *sc)
{
	struct cn30xxpip_attach_args pip_aa;

	pip_aa.aa_port = sc->sc_port;
	pip_aa.aa_regt = sc->sc_regt;
	pip_aa.aa_tag_type = POW_TAG_TYPE_ORDERED/* XXX */;
	pip_aa.aa_receive_group = OCTEON_POW_GROUP_PIP;
	pip_aa.aa_ip_offset = sc->sc_ip_offset;
	cn30xxpip_init(&pip_aa, &sc->sc_pip);
}

/* XXX */
void
octeon_eth_ipd_init(struct octeon_eth_softc *sc)
{
	struct cn30xxipd_attach_args ipd_aa;

	ipd_aa.aa_port = sc->sc_port;
	ipd_aa.aa_regt = sc->sc_regt;
	ipd_aa.aa_first_mbuff_skip = 184/* XXX */;
	ipd_aa.aa_not_first_mbuff_skip = 0/* XXX */;
	cn30xxipd_init(&ipd_aa, &sc->sc_ipd);
}

/* XXX */
void
octeon_eth_pko_init(struct octeon_eth_softc *sc)
{
	struct cn30xxpko_attach_args pko_aa;

	pko_aa.aa_port = sc->sc_port;
	pko_aa.aa_regt = sc->sc_regt;
	pko_aa.aa_cmdptr = &sc->sc_cmdptr;
	pko_aa.aa_cmd_buf_pool = OCTEON_POOL_NO_CMD;
	pko_aa.aa_cmd_buf_size = OCTEON_POOL_NWORDS_CMD;
	cn30xxpko_init(&pko_aa, &sc->sc_pko);
}

/* XXX */
void
octeon_eth_asx_init(struct octeon_eth_softc *sc)
{
	struct cn30xxasx_attach_args asx_aa;

	asx_aa.aa_port = sc->sc_port;
	asx_aa.aa_regt = sc->sc_regt;
	cn30xxasx_init(&asx_aa, &sc->sc_asx);
}

void
octeon_eth_smi_init(struct octeon_eth_softc *sc)
{
	struct cn30xxsmi_attach_args smi_aa;

	smi_aa.aa_port = sc->sc_port;
	smi_aa.aa_regt = sc->sc_regt;
	cn30xxsmi_init(&smi_aa, &sc->sc_smi);
	cn30xxsmi_set_clock(sc->sc_smi, 0x1464ULL); /* XXX */
}

/* ---- XXX */

void
octeon_eth_board_mac_addr(uint8_t *enaddr)
{
	int id;

	/* Initialize MAC addresses from the global address base. */
	if (octeon_eth_mac_addr == 0) {
		memcpy((uint8_t *)&octeon_eth_mac_addr + 2,
		    octeon_boot_info->mac_addr_base, 6);

		/*
		 * Should be allowed to fail hard if couldn't read the
		 * mac_addr_base address...
		 */
		if (octeon_eth_mac_addr == 0)
			return;

		/*
		 * Calculate the offset from the mac_addr_base that will be used
		 * for the next sc->sc_port.
		 */
		id = octeon_get_chipid();

		switch (octeon_model_family(id)) {
		case OCTEON_MODEL_FAMILY_CN56XX:
			octeon_eth_mac_addr_offset = 1;
			break;
		/*
		case OCTEON_MODEL_FAMILY_CN52XX:
		case OCTEON_MODEL_FAMILY_CN63XX:
			octeon_eth_mac_addr_offset = 2;
			break;
		*/
		default:
			octeon_eth_mac_addr_offset = 0;
			break;
		}

		enaddr += octeon_eth_mac_addr_offset;
	}

	/* No more MAC addresses to assign. */
	if (octeon_eth_mac_addr_offset >= octeon_boot_info->mac_addr_count)
		return;

	if (enaddr)
		memcpy(enaddr, (uint8_t *)&octeon_eth_mac_addr + 2, 6);

	octeon_eth_mac_addr++;
	octeon_eth_mac_addr_offset++;
}

/* ---- media */

int
octeon_eth_mii_readreg(struct device *self, int phy_no, int reg)
{
	struct octeon_eth_softc *sc = (struct octeon_eth_softc *)self;
	return cn30xxsmi_read(sc->sc_smi, phy_no, reg);
}

void
octeon_eth_mii_writereg(struct device *self, int phy_no, int reg, int value)
{
	struct octeon_eth_softc *sc = (struct octeon_eth_softc *)self;
	cn30xxsmi_write(sc->sc_smi, phy_no, reg, value);
}

void
octeon_eth_mii_statchg(struct device *self)
{
	struct octeon_eth_softc *sc = (struct octeon_eth_softc *)self;
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;

	cn30xxpko_port_enable(sc->sc_pko, 0);
	cn30xxgmx_port_enable(sc->sc_gmx_port, 0);

	octeon_eth_reset(sc);

	if (ISSET(ifp->if_flags, IFF_RUNNING))
		cn30xxgmx_set_filter(sc->sc_gmx_port);

	cn30xxpko_port_enable(sc->sc_pko, 1);
	cn30xxgmx_port_enable(sc->sc_gmx_port, 1);
}

int
octeon_eth_mediainit(struct octeon_eth_softc *sc)
{
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	struct mii_softc *child;

	sc->sc_mii.mii_ifp = ifp;
	sc->sc_mii.mii_readreg = octeon_eth_mii_readreg;
	sc->sc_mii.mii_writereg = octeon_eth_mii_writereg;
	sc->sc_mii.mii_statchg = octeon_eth_mii_statchg;
	ifmedia_init(&sc->sc_mii.mii_media, 0, octeon_eth_mediachange,
	    octeon_eth_mediastatus);

	mii_attach(&sc->sc_dev, &sc->sc_mii,
	    0xffffffff, sc->sc_phy_addr, MII_OFFSET_ANY, MIIF_DOPAUSE);

	child = LIST_FIRST(&sc->sc_mii.mii_phys);
	if (child == NULL) {
                /* No PHY attached. */
		ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER | IFM_MANUAL,
			    0, NULL);
		ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_MANUAL);
	} else {
		ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_AUTO);
	}

	return 0;
}

void
octeon_eth_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
	struct octeon_eth_softc *sc = ifp->if_softc;

	mii_pollstat(&sc->sc_mii);
	ifmr->ifm_status = sc->sc_mii.mii_media_status;
	ifmr->ifm_active = sc->sc_mii.mii_media_active;
	ifmr->ifm_active = (sc->sc_mii.mii_media_active & ~IFM_ETH_FMASK) |
	    sc->sc_gmx_port->sc_port_flowflags;
}

int
octeon_eth_mediachange(struct ifnet *ifp)
{
	struct octeon_eth_softc *sc = ifp->if_softc;

	if ((ifp->if_flags & IFF_UP) == 0)
		return 0;

	return mii_mediachg(&sc->sc_mii);
}

/* ---- send buffer garbage collection */

void
octeon_eth_send_queue_flush_prefetch(struct octeon_eth_softc *sc)
{
	OCTEON_ETH_KASSERT(sc->sc_prefetch == 0);
	cn30xxfau_op_inc_fetch_8(&sc->sc_fau_done, 0);
	sc->sc_prefetch = 1;
}

void
octeon_eth_send_queue_flush_fetch(struct octeon_eth_softc *sc)
{
#ifndef  OCTEON_ETH_DEBUG
	if (!sc->sc_prefetch)
		return;
#endif
	OCTEON_ETH_KASSERT(sc->sc_prefetch == 1);
	sc->sc_hard_done_cnt = cn30xxfau_op_inc_read_8(&sc->sc_fau_done);
	OCTEON_ETH_KASSERT(sc->sc_hard_done_cnt <= 0);
	sc->sc_prefetch = 0;
}

void
octeon_eth_send_queue_flush(struct octeon_eth_softc *sc)
{
	const int64_t sent_count = sc->sc_hard_done_cnt;
	int i;

	OCTEON_ETH_KASSERT(sc->sc_flush == 0);
	OCTEON_ETH_KASSERT(sent_count <= 0);

	for (i = 0; i < 0 - sent_count; i++) {
		struct mbuf *m;
		uint64_t *gbuf;

		octeon_eth_send_queue_del(sc, &m, &gbuf);

		cn30xxfpa_buf_put_paddr(octeon_eth_fb_sg, XKPHYS_TO_PHYS(gbuf));

		m_freem(m);
	}

	cn30xxfau_op_inc_fetch_8(&sc->sc_fau_done, i);
	sc->sc_flush = i;
}

void
octeon_eth_send_queue_flush_sync(struct octeon_eth_softc *sc)
{
	if (sc->sc_flush == 0)
		return;

	OCTEON_ETH_KASSERT(sc->sc_flush > 0);

	/* XXX */
	cn30xxfau_op_inc_read_8(&sc->sc_fau_done);
	sc->sc_soft_req_cnt -= sc->sc_flush;
	OCTEON_ETH_KASSERT(sc->sc_soft_req_cnt >= 0);
	/* XXX */

	sc->sc_flush = 0;
}

int
octeon_eth_send_queue_is_full(struct octeon_eth_softc *sc)
{
#ifdef OCTEON_ETH_SEND_QUEUE_CHECK
	int64_t nofree_cnt;

	nofree_cnt = sc->sc_soft_req_cnt + sc->sc_hard_done_cnt;

	if (__predict_false(nofree_cnt == GATHER_QUEUE_SIZE - 1)) {
		octeon_eth_send_queue_flush(sc);
		octeon_eth_send_queue_flush_sync(sc);
		return 1;
	}

#endif
	return 0;
}

void
octeon_eth_send_queue_add(struct octeon_eth_softc *sc, struct mbuf *m,
    uint64_t *gbuf)
{
	OCTEON_ETH_KASSERT(m->m_flags & M_PKTHDR);

	m->m_pkthdr.ph_cookie = gbuf;
	ml_enqueue(&sc->sc_sendq, m);

	if (m->m_ext.ext_free != NULL)
		sc->sc_ext_callback_cnt++;
}

void
octeon_eth_send_queue_del(struct octeon_eth_softc *sc, struct mbuf **rm,
    uint64_t **rgbuf)
{
	struct mbuf *m;
	m = ml_dequeue(&sc->sc_sendq);
	OCTEON_ETH_KASSERT(m != NULL);

	*rm = m;
	*rgbuf = m->m_pkthdr.ph_cookie;

	if (m->m_ext.ext_free != NULL) {
		sc->sc_ext_callback_cnt--;
		OCTEON_ETH_KASSERT(sc->sc_ext_callback_cnt >= 0);
	}
}

int
octeon_eth_buf_free_work(struct octeon_eth_softc *sc, uint64_t *work,
    uint64_t word2)
{
	/* XXX when jumbo frame */
	if (ISSET(word2, PIP_WQE_WORD2_IP_BUFS)) {
		paddr_t addr;
		paddr_t start_buffer;

		addr = XKPHYS_TO_PHYS(work[3] & PIP_WQE_WORD3_ADDR);
		start_buffer = addr & ~(2048 - 1);

		cn30xxfpa_buf_put_paddr(octeon_eth_fb_pkt, start_buffer);
	}

	cn30xxfpa_buf_put_paddr(octeon_eth_fb_wqe, XKPHYS_TO_PHYS(work));

	return 0;
}

void
octeon_eth_buf_ext_free_m(caddr_t buf, u_int size, void *arg)
{
	uint64_t *work = (void *)arg;
	int s = splnet();

	cn30xxfpa_buf_put_paddr(octeon_eth_fb_wqe, XKPHYS_TO_PHYS(work));

	splx(s);
}

void
octeon_eth_buf_ext_free_ext(caddr_t buf, u_int size,
    void *arg)
{
	uint64_t *work = (void *)arg;
	int s = splnet();

	cn30xxfpa_buf_put_paddr(octeon_eth_fb_wqe, XKPHYS_TO_PHYS(work));
	cn30xxfpa_buf_put_paddr(octeon_eth_fb_pkt, XKPHYS_TO_PHYS(buf));

	splx(s);
}

/* ---- ifnet interfaces */

int
octeon_eth_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct octeon_eth_softc *sc = ifp->if_softc;
	struct ifaddr *ifa = (struct ifaddr *)data;
	struct ifreq *ifr = (struct ifreq *)data;
	int s, error = 0;

	s = splnet();

	switch (cmd) {
	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;
		if (!(ifp->if_flags & IFF_RUNNING))
			octeon_eth_init(ifp);
		if (ifa->ifa_addr->sa_family == AF_INET)
			arp_ifinit(&sc->sc_arpcom, ifa);
		break;

	case SIOCSIFFLAGS:
		if (ifp->if_flags & IFF_UP) {
			if (ifp->if_flags & IFF_RUNNING)
				error = ENETRESET;
			else
				octeon_eth_init(ifp);
		} else {
			if (ifp->if_flags & IFF_RUNNING)
				octeon_eth_stop(ifp, 0);
		}
		break;

	case SIOCSIFMEDIA:
		/* Flow control requires full-duplex mode. */
		if (IFM_SUBTYPE(ifr->ifr_media) == IFM_AUTO ||
		    (ifr->ifr_media & IFM_FDX) == 0) {
			ifr->ifr_media &= ~IFM_ETH_FMASK;
		}
		if (IFM_SUBTYPE(ifr->ifr_media) != IFM_AUTO) {
			if ((ifr->ifr_media & IFM_ETH_FMASK) == IFM_FLOW) {
				ifr->ifr_media |=
				    IFM_ETH_TXPAUSE | IFM_ETH_RXPAUSE;
			}
			sc->sc_gmx_port->sc_port_flowflags =
				ifr->ifr_media & IFM_ETH_FMASK;
		}
		/* FALLTHROUGH */
	case SIOCGIFMEDIA:
		error = ifmedia_ioctl(ifp, ifr, &sc->sc_mii.mii_media, cmd);
		break;

	default:
		error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data);
	}

	if (error == ENETRESET) {
		if (ISSET(ifp->if_flags, IFF_RUNNING))
			cn30xxgmx_set_filter(sc->sc_gmx_port);
		error = 0;
	}

	octeon_eth_start(ifp);

	splx(s);
	return (error);
}

/* ---- send (output) */

uint64_t
octeon_eth_send_makecmd_w0(uint64_t fau0, uint64_t fau1, size_t len, int segs)
{
	return cn30xxpko_cmd_word0(
		OCT_FAU_OP_SIZE_64,		/* sz1 */
		OCT_FAU_OP_SIZE_64,		/* sz0 */
		1, fau1, 1, fau0,		/* s1, reg1, s0, reg0 */
		0,				/* le */
		octeon_eth_param_pko_cmd_w0_n2,	/* n2 */
		1, 0,				/* q, r */
		(segs == 1) ? 0 : 1,		/* g */
		0, 0, 1,			/* ipoffp1, ii, df */
		segs, (int)len);		/* segs, totalbytes */
}

uint64_t
octeon_eth_send_makecmd_w1(int size, paddr_t addr)
{
	return cn30xxpko_cmd_word1(
		0, 0,				/* i, back */
		FPA_GATHER_BUFFER_POOL,		/* pool */
		size, addr);			/* size, addr */
}

/* TODO: use bus_dma(9) */

#define KVTOPHYS(addr)	if_cnmac_kvtophys((vaddr_t)(addr))
paddr_t if_cnmac_kvtophys(vaddr_t);

paddr_t
if_cnmac_kvtophys(vaddr_t kva)
{
	if (IS_XKPHYS(kva))
		return XKPHYS_TO_PHYS(kva);
	else if (kva >= CKSEG0_BASE && kva < CKSEG0_BASE + CKSEG_SIZE)
		return CKSEG0_TO_PHYS(kva);
	else if (kva >= CKSEG1_BASE && kva < CKSEG1_BASE + CKSEG_SIZE)
		return CKSEG1_TO_PHYS(kva);

	printf("kva %lx is not able to convert physical address\n", kva);
	panic("if_cnmac_kvtophys");
}

int
octeon_eth_send_makecmd_gbuf(struct octeon_eth_softc *sc, struct mbuf *m0,
    uint64_t *gbuf, int *rsegs)
{
	struct mbuf *m;
	int segs = 0;
	uint32_t laddr, rlen, nlen;

	for (m = m0; m != NULL; m = m->m_next) {

		if (__predict_false(m->m_len == 0))
			continue;

#if 0
		OCTEON_ETH_KASSERT(((uint32_t)m->m_data & (PAGE_SIZE - 1))
		   == (kvtophys((vaddr_t)m->m_data) & (PAGE_SIZE - 1)));
#endif

		/*
		 * aligned 4k
		 */
		laddr = (uintptr_t)m->m_data & (PAGE_SIZE - 1);

		if (laddr + m->m_len > PAGE_SIZE) {
			/* XXX */
			rlen = PAGE_SIZE - laddr;
			nlen = m->m_len - rlen;
			*(gbuf + segs) = octeon_eth_send_makecmd_w1(rlen,
			    KVTOPHYS(m->m_data));
			segs++;
			if (segs > 63) {
				return 1;
			}
			/* XXX */
		} else {
			rlen = 0;
			nlen = m->m_len;
		}

		*(gbuf + segs) = octeon_eth_send_makecmd_w1(nlen,
		    KVTOPHYS((caddr_t)m->m_data + rlen));
		segs++;
		if (segs > 63) {
			return 1;
		}
	}

	OCTEON_ETH_KASSERT(m == NULL);

	*rsegs = segs;

	return 0;
}

int
octeon_eth_send_makecmd(struct octeon_eth_softc *sc, struct mbuf *m,
    uint64_t *gbuf, uint64_t *rpko_cmd_w0, uint64_t *rpko_cmd_w1)
{
	uint64_t pko_cmd_w0, pko_cmd_w1;
	int segs;
	int result = 0;

	if (octeon_eth_send_makecmd_gbuf(sc, m, gbuf, &segs)) {
		log(LOG_WARNING, "%s: large number of transmission"
		    " data segments", sc->sc_dev.dv_xname);
		result = 1;
		goto done;
	}

	/*
	 * segs == 1	-> link mode (single continuous buffer)
	 *		   WORD1[size] is number of bytes pointed by segment
	 *
	 * segs > 1	-> gather mode (scatter-gather buffer)
	 *		   WORD1[size] is number of segments
	 */
	pko_cmd_w0 = octeon_eth_send_makecmd_w0(sc->sc_fau_done.fd_regno,
	    0, m->m_pkthdr.len, segs);
	pko_cmd_w1 = octeon_eth_send_makecmd_w1(
	    (segs == 1) ? m->m_pkthdr.len : segs,
	    (segs == 1) ?
		KVTOPHYS(m->m_data) :
		XKPHYS_TO_PHYS(gbuf));

	*rpko_cmd_w0 = pko_cmd_w0;
	*rpko_cmd_w1 = pko_cmd_w1;

done:
	return result;
}

int
octeon_eth_send_cmd(struct octeon_eth_softc *sc, uint64_t pko_cmd_w0,
    uint64_t pko_cmd_w1)
{
	uint64_t *cmdptr;
	int result = 0;

	cmdptr = (uint64_t *)PHYS_TO_XKPHYS(sc->sc_cmdptr.cmdptr, CCA_CACHED);
	cmdptr += sc->sc_cmdptr.cmdptr_idx;

	OCTEON_ETH_KASSERT(cmdptr != NULL);

	*cmdptr++ = pko_cmd_w0;
	*cmdptr++ = pko_cmd_w1;

	OCTEON_ETH_KASSERT(sc->sc_cmdptr.cmdptr_idx + 2 <= FPA_COMMAND_BUFFER_POOL_NWORDS - 1);

	if (sc->sc_cmdptr.cmdptr_idx + 2 == FPA_COMMAND_BUFFER_POOL_NWORDS - 1) {
		paddr_t buf;

		buf = cn30xxfpa_buf_get_paddr(octeon_eth_fb_cmd);
		if (buf == 0) {
			log(LOG_WARNING,
			    "%s: cannot allocate command buffer from free pool allocator\n",
			    sc->sc_dev.dv_xname);
			result = 1;
			goto done;
		}
		*cmdptr++ = buf;
		sc->sc_cmdptr.cmdptr = (uint64_t)buf;
		sc->sc_cmdptr.cmdptr_idx = 0;
	} else {
		sc->sc_cmdptr.cmdptr_idx += 2;
	}

	cn30xxpko_op_doorbell_write(sc->sc_port, sc->sc_port, 2);

done:
	return result;
}

int
octeon_eth_send_buf(struct octeon_eth_softc *sc, struct mbuf *m,
    uint64_t *gbuf)
{
	int result = 0, error;
	uint64_t pko_cmd_w0, pko_cmd_w1;

	error = octeon_eth_send_makecmd(sc, m, gbuf, &pko_cmd_w0, &pko_cmd_w1);
	if (error != 0) {
		/* already logging */
		result = error;
		goto done;
	}

	error = octeon_eth_send_cmd(sc, pko_cmd_w0, pko_cmd_w1);
	if (error != 0) {
		/* already logging */
		result = error;
	}

done:
	return result;
}

int
octeon_eth_send(struct octeon_eth_softc *sc, struct mbuf *m)
{
	paddr_t gaddr = 0;
	uint64_t *gbuf = NULL;
	int result = 0, error;

	gaddr = cn30xxfpa_buf_get_paddr(octeon_eth_fb_sg);
	if (gaddr == 0) {
		log(LOG_WARNING,
		    "%s: cannot allocate gather buffer from free pool allocator\n",
		    sc->sc_dev.dv_xname);
		result = 1;
		goto done;
	}

	gbuf = (uint64_t *)(uintptr_t)PHYS_TO_XKPHYS(gaddr, CCA_CACHED);

	error = octeon_eth_send_buf(sc, m, gbuf);
	if (error != 0) {
		/* already logging */
		cn30xxfpa_buf_put_paddr(octeon_eth_fb_sg, gaddr);
		result = error;
		goto done;
	}

	octeon_eth_send_queue_add(sc, m, gbuf);

done:
	return result;
}

void
octeon_eth_start(struct ifnet *ifp)
{
	struct octeon_eth_softc *sc = ifp->if_softc;
	struct mbuf *m;

	/*
	 * performance tuning
	 * presend iobdma request
	 */
	octeon_eth_send_queue_flush_prefetch(sc);

	if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
		goto last;

	/* XXX assume that OCTEON doesn't buffer packets */
	if (__predict_false(!cn30xxgmx_link_status(sc->sc_gmx_port))) {
		/* dequeue and drop them */
		while (1) {
			IFQ_DEQUEUE(&ifp->if_snd, m);
			if (m == NULL)
				break;
#if 0
#ifdef DDB
			m_print(m, "cd", printf);
#endif
			printf("%s: drop\n", sc->sc_dev.dv_xname);
#endif
			m_freem(m);
			IF_DROP(&ifp->if_snd);
		}
		goto last;
	}

	for (;;) {
		IFQ_POLL(&ifp->if_snd, m);
		if (__predict_false(m == NULL))
			break;

		octeon_eth_send_queue_flush_fetch(sc); /* XXX */

		/*
		 * XXXSEIL
		 * If no free send buffer is available, free all the sent buffer
		 * and bail out.
		 */
		if (octeon_eth_send_queue_is_full(sc)) {
			return;
		}
		/* XXX */

		IFQ_DEQUEUE(&ifp->if_snd, m);

		OCTEON_ETH_TAP(ifp, m, BPF_DIRECTION_OUT);

		/* XXX */
		if (sc->sc_soft_req_cnt > sc->sc_soft_req_thresh)
			octeon_eth_send_queue_flush(sc);
		if (octeon_eth_send(sc, m)) {
			ifp->if_oerrors++;
			m_freem(m);
			log(LOG_WARNING,
		  	  "%s: failed to transmit packet\n",
		    	  sc->sc_dev.dv_xname);
		} else {
			sc->sc_soft_req_cnt++;
		}
		if (sc->sc_flush)
			octeon_eth_send_queue_flush_sync(sc);
		/* XXX */

		/*
		 * send next iobdma request
		 */
		octeon_eth_send_queue_flush_prefetch(sc);
	}

/*
 * XXXSEIL
 * Don't schedule send-buffer-free callout every time - those buffers are freed
 * by "free tick".  This makes some packets like NFS slower, but it normally
 * doesn't happen on SEIL.
 */
#ifdef OCTEON_ETH_USENFS
	if (__predict_false(sc->sc_ext_callback_cnt > 0)) {
		int timo;

		/* ??? */
		timo = hz - (100 * sc->sc_ext_callback_cnt);
		if (timo < 10)
			timo = 10;
		callout_schedule(&sc->sc_tick_free_ch, timo);
	}
#endif

last:
	octeon_eth_send_queue_flush_fetch(sc);
}

void
octeon_eth_watchdog(struct ifnet *ifp)
{
	struct octeon_eth_softc *sc = ifp->if_softc;

	printf("%s: device timeout\n", sc->sc_dev.dv_xname);

	octeon_eth_configure(sc);

	SET(ifp->if_flags, IFF_RUNNING);
	CLR(ifp->if_flags, IFF_OACTIVE);
	ifp->if_timer = 0;

	octeon_eth_start(ifp);
}

int
octeon_eth_init(struct ifnet *ifp)
{
	struct octeon_eth_softc *sc = ifp->if_softc;

	/* XXX don't disable commonly used parts!!! XXX */
	if (sc->sc_init_flag == 0) {
		/* Cancel any pending I/O. */
		octeon_eth_stop(ifp, 0);

		/* Initialize the device */
		octeon_eth_configure(sc);

		cn30xxpko_enable(sc->sc_pko);
		cn30xxipd_enable(sc->sc_ipd);

		sc->sc_init_flag = 1;
	} else {
		cn30xxgmx_port_enable(sc->sc_gmx_port, 1);
	}
	octeon_eth_mediachange(ifp);

	cn30xxgmx_set_filter(sc->sc_gmx_port);

	timeout_add_sec(&sc->sc_tick_misc_ch, 1);
	timeout_add_sec(&sc->sc_tick_free_ch, 1);

	SET(ifp->if_flags, IFF_RUNNING);
	CLR(ifp->if_flags, IFF_OACTIVE);

	return 0;
}

int
octeon_eth_stop(struct ifnet *ifp, int disable)
{
	struct octeon_eth_softc *sc = ifp->if_softc;

	timeout_del(&sc->sc_tick_misc_ch);
	timeout_del(&sc->sc_tick_free_ch);
	timeout_del(&sc->sc_resume_ch);

	mii_down(&sc->sc_mii);

	cn30xxgmx_port_enable(sc->sc_gmx_port, 0);

	/* Mark the interface as down and cancel the watchdog timer. */
	CLR(ifp->if_flags, IFF_RUNNING | IFF_OACTIVE);
	ifp->if_timer = 0;

	return 0;
}

/* ---- misc */

#define PKO_INDEX_MASK	((1ULL << 12/* XXX */) - 1)

int
octeon_eth_reset(struct octeon_eth_softc *sc)
{
	cn30xxgmx_reset_speed(sc->sc_gmx_port);
	cn30xxgmx_reset_flowctl(sc->sc_gmx_port);
	cn30xxgmx_reset_timing(sc->sc_gmx_port);
	cn30xxgmx_reset_board(sc->sc_gmx_port);

	return 0;
}

int
octeon_eth_configure(struct octeon_eth_softc *sc)
{
	cn30xxgmx_port_enable(sc->sc_gmx_port, 0);

	octeon_eth_reset(sc);

	octeon_eth_configure_common(sc);

	cn30xxpko_port_config(sc->sc_pko);
	cn30xxpko_port_enable(sc->sc_pko, 1);
	cn30xxpip_port_config(sc->sc_pip);

	cn30xxgmx_tx_stats_rd_clr(sc->sc_gmx_port, 1);
	cn30xxgmx_rx_stats_rd_clr(sc->sc_gmx_port, 1);

	cn30xxgmx_port_enable(sc->sc_gmx_port, 1);

	return 0;
}

int
octeon_eth_configure_common(struct octeon_eth_softc *sc)
{
	static int once;

	if (once == 1)
		return 0;
	once = 1;

#if 0
	octeon_eth_buf_init(sc);
#endif

	cn30xxipd_config(sc->sc_ipd);
	cn30xxpko_config(sc->sc_pko);

	cn30xxpow_config(sc->sc_pow, OCTEON_POW_GROUP_PIP);

	return 0;
}

int
octeon_eth_recv_mbuf(struct octeon_eth_softc *sc, uint64_t *work,
    struct mbuf **rm)
{
	struct mbuf *m;
	void (*ext_free)(caddr_t, u_int, void *);
	void *ext_buf;
	size_t ext_size;
	void *data;
	uint64_t word1 = work[1];
	uint64_t word2 = work[2];
	uint64_t word3 = work[3];

	MGETHDR(m, M_NOWAIT, MT_DATA);
	if (m == NULL)
		return 1;
	OCTEON_ETH_KASSERT(m != NULL);

	if ((word2 & PIP_WQE_WORD2_IP_BUFS) == 0) {
		/* Dynamic short */
		ext_free = octeon_eth_buf_ext_free_m;
		ext_buf = &work[4];
		ext_size = 96;

		data = &work[4 + sc->sc_ip_offset / sizeof(uint64_t)];
	} else {
		vaddr_t addr;
		vaddr_t start_buffer;

		addr = PHYS_TO_XKPHYS(word3 & PIP_WQE_WORD3_ADDR, CCA_CACHED);
		start_buffer = addr & ~(2048 - 1);

		ext_free = octeon_eth_buf_ext_free_ext;
		ext_buf = (void *)start_buffer;
		ext_size = 2048;

		data = (void *)addr;
	}

	MEXTADD(m, ext_buf, ext_size, 0, ext_free, work);
	OCTEON_ETH_KASSERT(ISSET(m->m_flags, M_EXT));

	m->m_data = data;
	m->m_len = m->m_pkthdr.len = (word1 & PIP_WQE_WORD1_LEN) >> 48;
#if 0
	/*
	 * not readonly buffer
	 */
	m->m_flags |= M_EXT_RW;
#endif

	*rm = m;

	OCTEON_ETH_KASSERT(*rm != NULL);

	return 0;
}

int
octeon_eth_recv_check_code(struct octeon_eth_softc *sc, uint64_t word2)
{
	uint64_t opecode = word2 & PIP_WQE_WORD2_NOIP_OPECODE;

	if (__predict_true(!ISSET(word2, PIP_WQE_WORD2_NOIP_RE)))
		return 0;

	/* this error is harmless */
	if (opecode == PIP_OVER_ERR)
		return 0;

	return 1;
}

#if 0 /* not used */
int
octeon_eth_recv_check_jumbo(struct octeon_eth_softc *sc, uint64_t word2)
{
	if (__predict_false((word2 & PIP_WQE_WORD2_IP_BUFS) > (1ULL << 56)))
		return 1;
	return 0;
}
#endif

int
octeon_eth_recv_check_link(struct octeon_eth_softc *sc, uint64_t word2)
{
	if (__predict_false(!cn30xxgmx_link_status(sc->sc_gmx_port)))
		return 1;
	return 0;
}

int
octeon_eth_recv_check(struct octeon_eth_softc *sc, uint64_t word2)
{
	if (__predict_false(octeon_eth_recv_check_link(sc, word2)) != 0) {
		if (ratecheck(&sc->sc_rate_recv_check_link_last,
		    &sc->sc_rate_recv_check_link_cap))
			log(LOG_DEBUG,
			    "%s: link is not up, the packet was dropped\n",
			    sc->sc_dev.dv_xname);
		return 1;
	}

#if 0 /* XXX Performance tuning (Jumbo-frame is not supported yet!) */
	if (__predict_false(octeon_eth_recv_check_jumbo(sc, word2)) != 0) {
		/* XXX jumbo frame */
		if (ratecheck(&sc->sc_rate_recv_check_jumbo_last,
		    &sc->sc_rate_recv_check_jumbo_cap))
			log(LOG_DEBUG,
			    "jumbo frame was received\n");
		return 1;
	}
#endif

	if (__predict_false(octeon_eth_recv_check_code(sc, word2)) != 0) {
		if ((word2 & PIP_WQE_WORD2_NOIP_OPECODE) == PIP_WQE_WORD2_RE_OPCODE_LENGTH) {
			/* no logging */
			/* XXX inclement special error count */
		} else if ((word2 & PIP_WQE_WORD2_NOIP_OPECODE) ==
				PIP_WQE_WORD2_RE_OPCODE_PARTIAL) {
			/* not an error. it's because of overload */
		}
		else {
			if (ratecheck(&sc->sc_rate_recv_check_code_last,
			    &sc->sc_rate_recv_check_code_cap))
				log(LOG_WARNING,
				    "%s: a reception error occured, "
				    "the packet was dropped (error code = %lld)\n",
				    sc->sc_dev.dv_xname, word2 & PIP_WQE_WORD2_NOIP_OPECODE);
		}
		return 1;
	}

	return 0;
}

int
octeon_eth_recv(struct octeon_eth_softc *sc, uint64_t *work)
{
	int result = 0;
	struct ifnet *ifp;
	struct mbuf_list ml = MBUF_LIST_INITIALIZER();
	struct mbuf *m;
	uint64_t word2;

	/* XXX */
	/*
 	 * performance tuning
	 * presend iobdma request
	 */
	if (sc->sc_soft_req_cnt > sc->sc_soft_req_thresh) {
		octeon_eth_send_queue_flush_prefetch(sc);
	}
	/* XXX */

	OCTEON_ETH_KASSERT(sc != NULL);
	OCTEON_ETH_KASSERT(work != NULL);

	word2 = work[2];
	ifp = &sc->sc_arpcom.ac_if;

	OCTEON_ETH_KASSERT(ifp != NULL);

	if (__predict_false(octeon_eth_recv_check(sc, word2) != 0)) {
		ifp->if_ierrors++;
		result = 1;
		octeon_eth_buf_free_work(sc, work, word2);
		goto drop;
	}

	if (__predict_false(octeon_eth_recv_mbuf(sc, work, &m) != 0)) {
		ifp->if_ierrors++;
		result = 1;
		octeon_eth_buf_free_work(sc, work, word2);
		goto drop;
	}

	/* work[0] .. work[3] may not be valid any more */

	OCTEON_ETH_KASSERT(m != NULL);

	cn30xxipd_offload(word2, m->m_data, &m->m_pkthdr.csum_flags);

	/* XXX */
	if (sc->sc_soft_req_cnt > sc->sc_soft_req_thresh) {
		octeon_eth_send_queue_flush_fetch(sc);
		octeon_eth_send_queue_flush(sc);
	}

	/* XXX */
	if (sc->sc_flush)
		octeon_eth_send_queue_flush_sync(sc);

	ml_enqueue(&ml, m);
	if_input(ifp, &ml);

	return 0;

drop:
	/* XXX */
	if (sc->sc_soft_req_cnt > sc->sc_soft_req_thresh) {
		octeon_eth_send_queue_flush_fetch(sc);
	}
	/* XXX */

	return result;
}

void
octeon_eth_recv_intr(void *data, uint64_t *work)
{
	struct octeon_eth_softc *sc;
	int port;

	OCTEON_ETH_KASSERT(work != NULL);

	port = (work[1] & PIP_WQE_WORD1_IPRT) >> 42;

	OCTEON_ETH_KASSERT(port < GMX_PORT_NUNITS);

	sc = octeon_eth_gsc[port];

	OCTEON_ETH_KASSERT(sc != NULL);
	OCTEON_ETH_KASSERT(port == sc->sc_port);

	/* XXX process all work queue entries anyway */

	(void)octeon_eth_recv(sc, work);
}

/* ---- tick */

/*
 * octeon_eth_tick_free
 *
 * => garbage collect send gather buffer / mbuf
 * => called at softclock
 */
void
octeon_eth_tick_free(void *arg)
{
	struct octeon_eth_softc *sc = arg;
	int timo;
	int s;

	s = splnet();
	/* XXX */
	if (sc->sc_soft_req_cnt > 0) {
		octeon_eth_send_queue_flush_prefetch(sc);
		octeon_eth_send_queue_flush_fetch(sc);
		octeon_eth_send_queue_flush(sc);
		octeon_eth_send_queue_flush_sync(sc);
	}
	/* XXX */

	/* XXX ??? */
	timo = hz - (100 * sc->sc_ext_callback_cnt);
	if (timo < 10)
		 timo = 10;
	timeout_add_msec(&sc->sc_tick_free_ch, 1000 * timo / hz);
	/* XXX */
	splx(s);
}

/*
 * octeon_eth_tick_misc
 *
 * => collect statistics
 * => check link status
 * => called at softclock
 */
void
octeon_eth_tick_misc(void *arg)
{
	struct octeon_eth_softc *sc = arg;
	struct ifnet *ifp;
	u_quad_t iqdrops, delta;
	int s;

	s = splnet();

	ifp = &sc->sc_arpcom.ac_if;

	iqdrops = ifp->if_iqdrops;
	cn30xxgmx_stats(sc->sc_gmx_port);
#ifdef OCTEON_ETH_DEBUG
	delta = ifp->if_iqdrops - iqdrops;
	printf("%s: %qu packets dropped at GMX FIFO\n",
			ifp->if_xname, delta);
#endif
	cn30xxpip_stats(sc->sc_pip, ifp, sc->sc_port);
	delta = ifp->if_iqdrops - iqdrops;
#ifdef OCTEON_ETH_DEBUG
	printf("%s: %qu packets dropped at PIP + GMX FIFO\n",
			ifp->if_xname, delta);
#endif

	mii_tick(&sc->sc_mii);

#ifdef OCTEON_ETH_FIXUP_ODD_NIBBLE_DYNAMIC
	if (sc->sc_gmx_port->sc_proc_nibble_by_soft &&
	    sc->sc_gmx_port->sc_even_nibble_cnt > PROC_NIBBLE_SOFT_THRESHOLD) {
#ifdef OCTEON_ETH_DEBUG
		log(LOG_DEBUG, "%s: even nibble preamble count %d\n",
		    sc->sc_dev.dv_xname, sc->sc_gmx_port->sc_even_nibble_cnt);
#endif
		if (OCTEON_ETH_FIXUP_ODD_NIBBLE_MODEL_P(sc) &&
		    OCTEON_ETH_FIXUP_ODD_NIBBLE_DYNAMIC_SPEED_P(sc->sc_gmx_port, ifp)) {
			log(LOG_NOTICE,
			    "%s: the preamble processing switched to hardware\n",
			    sc->sc_dev.dv_xname);
		}
		sc->sc_gmx_port->sc_proc_nibble_by_soft = 0;
		octeon_eth_mii_statchg((struct device *)sc);
		sc->sc_gmx_port->sc_even_nibble_cnt = 0;
	}
#endif
	splx(s);

	timeout_add_sec(&sc->sc_tick_misc_ch, 1);
}


code:
/*	$OpenBSD: if_cnmacvar.h,v 1.6 2015/06/11 12:30:42 jmatthew Exp $	*/

/*
 * Copyright (c) 2007 Internet Initiative Japan, Inc.
 * 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 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.
 */

#define IS_MAC_MULTICASTBIT(addr) \
        ((addr)[0] & 0x01)

#define SEND_QUEUE_SIZE		(32)
#define GATHER_QUEUE_SIZE	(1024)
#define FREE_QUEUE_SIZE		GATHER_QUEUE_SIZE
#define RECV_QUEUE_SIZE		(GATHER_QUEUE_SIZE * 2)

#ifdef OCTEON_ETH_FIXUP_ODD_NIBBLE_DYNAMIC
#define PROC_NIBBLE_SOFT_THRESHOLD 2000
#endif

struct _send_queue_entry;
struct cn30xxpow_softc;
struct cn30xxpip_softc;
struct cn30xxipd_softc;
struct cn30xxpko_softc;
struct cn30xxasx_softc;
struct cn30xxsmi_softc;
struct cn30xxgmx_port_softc;
struct cn30xxpow_softc;

extern struct cn30xxpow_softc	cn30xxpow_softc;

struct octeon_eth_softc {
	struct device		sc_dev;
	bus_space_tag_t		sc_regt;
	bus_dma_tag_t		sc_dmat;

	bus_dmamap_t		sc_dmap;

	void			*sc_pow_recv_ih;
	struct cn30xxpip_softc	*sc_pip;
	struct cn30xxipd_softc	*sc_ipd;
	struct cn30xxpko_softc	*sc_pko;
	struct cn30xxasx_softc	*sc_asx;
	struct cn30xxsmi_softc	*sc_smi;
	struct cn30xxgmx_softc	*sc_gmx;
	struct cn30xxgmx_port_softc
				*sc_gmx_port;
	struct cn30xxpow_softc
				*sc_pow;

	struct arpcom		sc_arpcom;
	struct mii_data		sc_mii;

	struct timeout		sc_tick_misc_ch;
	struct timeout		sc_tick_free_ch;
	struct timeout		sc_resume_ch;

	int64_t			sc_soft_req_cnt;
	int64_t			sc_soft_req_thresh;
	int64_t			sc_hard_done_cnt;
	int			sc_flush;
	int			sc_prefetch;
	struct mbuf_list	sc_sendq;
	uint64_t		sc_ext_callback_cnt;

	uint32_t		sc_port;
	uint32_t		sc_port_type;
	uint32_t		sc_init_flag;
	int			sc_phy_addr;

	/*
	 * Redirection - received (input) packets are redirected (directly sent)
	 * to another port.  Only meant to test hardware + driver performance.
	 *
	 *  0	- disabled
	 * >0	- redirected to ports that correspond to bits
	 *		0b001 (0x1)	- Port 0
	 *		0b010 (0x2)	- Port 1
	 *		0b100 (0x4)	- Port 2
	 */
	int			sc_redir;

	struct cn30xxfau_desc	sc_fau_done;
	struct cn30xxpko_cmdptr_desc
				sc_cmdptr;

	size_t			sc_ip_offset;

	struct timeval		sc_rate_recv_check_link_last;
	struct timeval		sc_rate_recv_check_link_cap;
	struct timeval		sc_rate_recv_check_jumbo_last;
	struct timeval		sc_rate_recv_check_jumbo_cap;
	struct timeval		sc_rate_recv_check_code_last;
	struct timeval		sc_rate_recv_check_code_cap;
};

license: unknown


code:
/*	$OpenBSD: iobusvar.h,v 1.3 2015/07/19 23:46:50 jasper Exp $	*/

/*
 * Copyright (c) 2001-2003 Opsycon AB  (www.opsycon.se / www.opsycon.com)
 *
 * 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.
 *
 */

#ifndef	_IOBUSVAR_H_
#define	_IOBUSVAR_H_

#include <machine/bus.h>

extern bus_space_t iobus_tag;
extern struct machine_bus_dma_tag iobus_dma_tag;

struct iobus_attach_args {
	char		*aa_name;
	int		aa_unitno;

	bus_addr_t	aa_addr;
	int		aa_irq;

	bus_space_tag_t	aa_bust;
	bus_dma_tag_t	aa_dmat;
};

struct octeon_iobus_addrs {
	const char	*name;
	bus_addr_t	address;
};

int	 iobus_space_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
	    bus_space_handle_t *);
void	 iobus_space_unmap(bus_space_tag_t, bus_space_handle_t, bus_size_t);
int	 iobus_space_region(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    bus_size_t, bus_space_handle_t *);

void	*iobus_space_vaddr(bus_space_tag_t, bus_space_handle_t);

#endif	/* _IOBUSVAR_H_ */


code:
/*	$OpenBSD: mainbus.c,v 1.7 2014/07/14 10:23:58 jasper Exp $ */

/*
 * Copyright (c) 2001-2003 Opsycon AB  (www.opsycon.se / www.opsycon.com)
 *
 * 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.
 *
 */

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

#include <machine/autoconf.h>

int	mainbus_match(struct device *, void *, void *);
void	mainbus_attach(struct device *, struct device *, void *);
int	mainbus_print(void *, const char *);

const struct cfattach mainbus_ca = {
	sizeof(struct device), mainbus_match, mainbus_attach
};

struct cfdriver mainbus_cd = {
	NULL, "mainbus", DV_DULL
};

int
mainbus_match(struct device *parent, void *cfdata, void *aux)
{
	static int mainbus_attached = 0;

	if (mainbus_attached != 0)
		return 0;

	return mainbus_attached = 1;
}

void
mainbus_attach(struct device *parent, struct device *self, void *aux)
{
	struct cpu_attach_args caa;
#ifdef MULTIPROCESSOR
	struct cpu_hwinfo hw;
#endif

	printf("\n");

	bzero(&caa, sizeof caa);
	caa.caa_maa.maa_name = "cpu";
	caa.caa_hw = &bootcpu_hwinfo;
	config_found(self, &caa, mainbus_print);

#ifdef MULTIPROCESSOR
	bcopy(&bootcpu_hwinfo, &hw, sizeof(struct cpu_hwinfo));
	caa.caa_hw = &hw;
	config_found(self, &caa, mainbus_print);
#endif

	caa.caa_maa.maa_name = "clock";
	config_found(self, &caa.caa_maa, mainbus_print);

	/* uart I/O */
	caa.caa_maa.maa_name = "uartbus";
	config_found(self, &caa.caa_maa, mainbus_print);

	/* on-board I/O */
	caa.caa_maa.maa_name = "iobus";
	config_found(self, &caa.caa_maa, mainbus_print);

	caa.caa_maa.maa_name = "octrtc";
	config_found(self, &caa.caa_maa, mainbus_print);

}

int
mainbus_print(void *aux, const char *pnp)
{
	return pnp != NULL ? QUIET : UNCONF;
}


code:
/*	$OpenBSD: octcf.c,v 1.27 2015/07/20 01:38:31 jasper Exp $ */
/*	$NetBSD: wd.c,v 1.193 1999/02/28 17:15:27 explorer Exp $ */

/*
 * Copyright (c) 1998, 2001 Manuel Bouyer.  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) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Charles M. Hannum and by Onno van der Linden.
 *
 * 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mutex.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/dkio.h>

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

#include <dev/ata/atareg.h>
#include <dev/ata/atavar.h>
#include <dev/ic/wdcreg.h>
#include <dev/ic/wdcvar.h>

#include <octeon/dev/iobusvar.h>
#include <machine/octeonreg.h>
#include <machine/octeonvar.h>

#define OCTCF_REG_SIZE	8
#define ATAPARAMS_SIZE	512
#define SECTOR_SIZE	512
#define OCTCFDELAY	100 /* 100 microseconds */
#define NR_TRIES	1000

#define DEBUG_XFERS  0x02
#define DEBUG_FUNCS  0x08
#define DEBUG_PROBE  0x10

#ifdef OCTCFDEBUG
int octcfdebug_mask = 0xff;
#define OCTCFDEBUG_PRINT(args, level) do {	\
	if ((octcfdebug_mask & (level)) != 0)	\
		printf args;			\
} while (0)
#else
#define OCTCFDEBUG_PRINT(args, level)
#endif

struct octcf_softc {
	/* General disk infos */
	struct device sc_dev;
	struct disk sc_dk;
	struct bufq sc_bufq;
	struct buf *sc_bp;
	struct ataparams sc_params;/* drive characteristics found */
	int sc_flags;
#define OCTCFF_LOADED		0x10 /* parameters loaded */
	u_int64_t sc_capacity;
	bus_space_tag_t       sc_iot;
	bus_space_handle_t    sc_ioh;
};

int	octcfprobe(struct device *, void *, void *);
void	octcfattach(struct device *, struct device *, void *);
int	octcfdetach(struct device *, int);
int	octcfactivate(struct device *, int);
int	octcfprint(void *, char *);

struct cfattach octcf_ca = {
	sizeof(struct octcf_softc), octcfprobe, octcfattach,
	octcfdetach, octcfactivate
};

struct cfdriver octcf_cd = {
	NULL, "octcf", DV_DISK
};

void  octcfgetdefaultlabel(struct octcf_softc *, struct disklabel *);
int   octcfgetdisklabel(dev_t dev, struct octcf_softc *, struct disklabel *, int);
void  octcfstrategy(struct buf *);
void  octcfstart(void *);
void  _octcfstart(struct octcf_softc*, struct buf *);
void  octcfdone(void *);

cdev_decl(octcf);
bdev_decl(octcf);

#define octcflookup(unit) (struct octcf_softc *)disk_lookup(&octcf_cd, (unit))

int	octcf_write_sectors(struct octcf_softc *, uint32_t, uint32_t, void *);
int	octcf_read_sectors(struct octcf_softc *, uint32_t, uint32_t, void *);
int	octcf_wait_busy(struct octcf_softc *);
void	octcf_command(struct octcf_softc *, uint32_t, uint8_t);
int 	octcf_get_params(struct octcf_softc *, struct ataparams *);

#define OCTCF_REG_READ(wd, reg) \
	bus_space_read_2(wd->sc_iot, wd->sc_ioh, reg & 0x6)
#define OCTCF_REG_WRITE(wd, reg, val) \
	bus_space_write_2(wd->sc_iot, wd->sc_ioh, reg & 0x6, val)

int
octcfprobe(struct device *parent, void *match, void *aux)
{
	if (octeon_boot_info->cf_common_addr == 0) {
		OCTCFDEBUG_PRINT(("%s: No cf bus found\n", __func__), DEBUG_FUNCS | DEBUG_PROBE);
		return 0;
	}

	return 1;
}

void
octcfattach(struct device *parent, struct device *self, void *aux)
{
	struct octcf_softc *wd = (void *)self;
	struct iobus_attach_args *aa = aux;
	int i, blank;
	char buf[41], c, *p, *q;
	uint8_t status;
	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS | DEBUG_PROBE);

	wd->sc_iot = aa->aa_bust;

	if (bus_space_map(wd->sc_iot, aa->aa_addr,
	    OCTCF_REG_SIZE, BUS_SPACE_MAP_KSEG0, &wd->sc_ioh)) {
		printf(": couldn't map registers\n");
		return;
	}

	for (i = 0; i < 8; i++) {
		uint64_t cfg =
		*(uint64_t *)PHYS_TO_XKPHYS(
			OCTEON_MIO_BOOT_BASE + MIO_BOOT_REG_CFG(i), CCA_NC);

		if ((cfg & BOOT_CFG_BASE_MASK) ==
			(OCTEON_CF_BASE >> BOOT_CFG_BASE_SHIFT)) {
			if ((cfg & BOOT_CFG_WIDTH_MASK) == 0)
				printf(": doesn't support 8bit cards\n");
			break;
		}
	}

	/* Check if CF is inserted */
	i = 0;
	while ( (status = (OCTCF_REG_READ(wd, wdr_status)>>8)) & WDCS_BSY) {
		if ((i++) == NR_TRIES )     {
			printf(": card not present\n");
			return;
               	}
		DELAY(OCTCFDELAY);
	}

	/* read our drive info */
	if (octcf_get_params(wd, &wd->sc_params) != 0) {
		printf(": IDENTIFY failed\n");
		return;
	}

	for (blank = 0, p = wd->sc_params.atap_model, q = buf, i = 0;
	    i < sizeof(wd->sc_params.atap_model); i++) {
		c = *p++;
		if (c == '\0')
			break;
		if (c != ' ') {
			if (blank) {
				*q++ = ' ';
				blank = 0;
			}
			*q++ = c;
		} else
			blank = 1;
		}
	*q++ = '\0';

	printf(": <%s>\n", buf);
	printf("%s: %d-sector PIO,",
		wd->sc_dev.dv_xname, wd->sc_params.atap_multi & 0xff);

	wd->sc_capacity =
		wd->sc_params.atap_cylinders *
		wd->sc_params.atap_heads *
		wd->sc_params.atap_sectors;
	printf(" CHS, %lluMB, %d cyl, %d head, %d sec, %llu sectors\n",
		wd->sc_capacity / (1048576 / DEV_BSIZE),
		wd->sc_params.atap_cylinders,
		wd->sc_params.atap_heads,
		wd->sc_params.atap_sectors,
		wd->sc_capacity);

	OCTCFDEBUG_PRINT(
		("%s: atap_dmatiming_mimi=%d, atap_dmatiming_recom=%d\n",
		self->dv_xname, wd->sc_params.atap_dmatiming_mimi,
		wd->sc_params.atap_dmatiming_recom), DEBUG_PROBE);

	/*
	 * Initialize disk structures.
	 */
	wd->sc_dk.dk_name = wd->sc_dev.dv_xname;
	bufq_init(&wd->sc_bufq, BUFQ_DEFAULT);

	/* Attach disk. */
	disk_attach(&wd->sc_dev, &wd->sc_dk);
}

int
octcfactivate(struct device *self, int act)
{
	return 0;
}

int
octcfdetach(struct device *self, int flags)
{
	struct octcf_softc *sc = (struct octcf_softc *)self;

	bufq_drain(&sc->sc_bufq);

	disk_gone(octcfopen, self->dv_unit);

	/* Detach disk. */
	bufq_destroy(&sc->sc_bufq);
	disk_detach(&sc->sc_dk);

	return (0);
}

/*
 * Read/write routine for a buffer.  Validates the arguments and schedules the
 * transfer.  Does not wait for the transfer to complete.
 */
void
octcfstrategy(struct buf *bp)
{
	struct octcf_softc *wd;
	int s;

	wd = octcflookup(DISKUNIT(bp->b_dev));
	if (wd == NULL) {
		bp->b_error = ENXIO;
		goto bad;
	}

	OCTCFDEBUG_PRINT(("%s (%s)\n", __func__, wd->sc_dev.dv_xname),
	    DEBUG_XFERS);

	/* If device invalidated (e.g. media change, door open), error. */
	if ((wd->sc_flags & OCTCFF_LOADED) == 0) {
		bp->b_error = EIO;
		goto bad;
	}

	/* Validate the request. */
	if (bounds_check_with_label(bp, wd->sc_dk.dk_label) == -1)
		goto done;

	/* Check that the number of sectors can fit in a byte. */
	if ((bp->b_bcount / wd->sc_dk.dk_label->d_secsize) >= (1 << NBBY)) {
		bp->b_error = EINVAL;
		goto bad;
	}

	/* Queue transfer on drive, activate drive and controller if idle. */
	bufq_queue(&wd->sc_bufq, bp);
	s = splbio();
	octcfstart(wd);
	splx(s);
	device_unref(&wd->sc_dev);
	return;

 bad:
	bp->b_flags |= B_ERROR;
	bp->b_resid = bp->b_bcount;
 done:
	s = splbio();
	biodone(bp);
	splx(s);
	if (wd != NULL)
		device_unref(&wd->sc_dev);
}

/*
 * Queue a drive for I/O.
 */
void
octcfstart(void *arg)
{
	struct octcf_softc *wd = arg;
	struct buf *bp;

	OCTCFDEBUG_PRINT(("%s %s\n", __func__, wd->sc_dev.dv_xname),
	    DEBUG_XFERS);
	while ((bp = bufq_dequeue(&wd->sc_bufq)) != NULL) {
		/* Transfer this buffer now. */
		_octcfstart(wd, bp);
	}
}

void
_octcfstart(struct octcf_softc *wd, struct buf *bp)
{
	struct disklabel *lp;
	u_int64_t secno;
	u_int64_t nsecs;

	lp = wd->sc_dk.dk_label;
	secno = DL_BLKTOSEC(lp, bp->b_blkno) +
	    DL_GETPOFFSET(&lp->d_partitions[DISKPART(bp->b_dev)]);
	nsecs = howmany(bp->b_bcount, lp->d_secsize);
	wd->sc_bp = bp;

	/* Instrumentation. */
	disk_busy(&wd->sc_dk);

	if (bp->b_flags & B_READ)
		bp->b_error = octcf_read_sectors(wd, nsecs, secno, bp->b_data);
	else
		bp->b_error = octcf_write_sectors(wd, nsecs, secno, bp->b_data);

	octcfdone(wd);
}

void
octcfdone(void *arg)
{
	struct octcf_softc *wd = arg;
	struct buf *bp = wd->sc_bp;

	if (bp->b_error == 0)
		bp->b_resid = 0;
	else
		bp->b_flags |= B_ERROR;

	disk_unbusy(&wd->sc_dk, (bp->b_bcount - bp->b_resid),
	    (bp->b_flags & B_READ));
	biodone(bp);
}

int
octcfread(dev_t dev, struct uio *uio, int flags)
{

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_XFERS);
	return (physio(octcfstrategy, dev, B_READ, minphys, uio));
}

int
octcfwrite(dev_t dev, struct uio *uio, int flags)
{

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_XFERS);
	return (physio(octcfstrategy, dev, B_WRITE, minphys, uio));
}

int
octcfopen(dev_t dev, int flag, int fmt, struct proc *p)
{
	struct octcf_softc *wd;
	int unit, part;
	int error;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	unit = DISKUNIT(dev);
	wd = octcflookup(unit);
	if (wd == NULL)
		return ENXIO;

	/*
	 * If this is the first open of this device, add a reference
	 * to the adapter.
	 */
	if ((error = disk_lock(&wd->sc_dk)) != 0)
		goto bad4;

	if (wd->sc_dk.dk_openmask != 0) {
		/*
		 * If any partition is open, but the disk has been invalidated,
		 * disallow further opens.
		 */
		if ((wd->sc_flags & OCTCFF_LOADED) == 0) {
			error = EIO;
			goto bad3;
		}
	} else {
		if ((wd->sc_flags & OCTCFF_LOADED) == 0) {
			wd->sc_flags |= OCTCFF_LOADED;

			/* Load the physical device parameters. */
			octcf_get_params(wd, &wd->sc_params);

			/* Load the partition info if not already loaded. */
			if (octcfgetdisklabel(dev, wd,
			    wd->sc_dk.dk_label, 0) == EIO) {
				error = EIO;
				goto bad;
			}
		}
	}

	part = DISKPART(dev);

	if ((error = disk_openpart(&wd->sc_dk, part, fmt, 1)) != 0)
		goto bad;

	disk_unlock(&wd->sc_dk);
	device_unref(&wd->sc_dev);
	return 0;

bad:
	if (wd->sc_dk.dk_openmask == 0) {
	}

bad3:
	disk_unlock(&wd->sc_dk);
bad4:
	device_unref(&wd->sc_dev);
	return error;
}

int
octcfclose(dev_t dev, int flag, int fmt, struct proc *p)
{
	struct octcf_softc *wd;
	int part = DISKPART(dev);

	wd = octcflookup(DISKUNIT(dev));
	if (wd == NULL)
		return ENXIO;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	disk_lock_nointr(&wd->sc_dk);

	disk_closepart(&wd->sc_dk, part, fmt);

	disk_unlock(&wd->sc_dk);

	device_unref(&wd->sc_dev);
	return (0);
}

void
octcfgetdefaultlabel(struct octcf_softc *wd, struct disklabel *lp)
{
	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);
	bzero(lp, sizeof(struct disklabel));

	lp->d_secsize = DEV_BSIZE;
	DL_SETDSIZE(lp, wd->sc_capacity);
	lp->d_ntracks = wd->sc_params.atap_heads;
	lp->d_nsectors = wd->sc_params.atap_sectors;
	lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
	lp->d_ncylinders = DL_GETDSIZE(lp) / lp->d_secpercyl;
	lp->d_type = DTYPE_ESDI;
	strncpy(lp->d_typename, "ESDI/IDE disk", sizeof lp->d_typename);

	/* XXX - user viscopy() like sd.c */
	strncpy(lp->d_packname, wd->sc_params.atap_model, sizeof lp->d_packname);
	lp->d_flags = 0;
	lp->d_version = 1;

	lp->d_magic = DISKMAGIC;
	lp->d_magic2 = DISKMAGIC;
	lp->d_checksum = dkcksum(lp);
}

/*
 * Fabricate a default disk label, and try to read the correct one.
 */
int
octcfgetdisklabel(dev_t dev, struct octcf_softc *wd, struct disklabel *lp,
    int spoofonly)
{
	int error;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	octcfgetdefaultlabel(wd, lp);
	error = readdisklabel(DISKLABELDEV(dev), octcfstrategy, lp,
	    spoofonly);
	return (error);
}

int
octcfioctl(dev_t dev, u_long xfer, caddr_t addr, int flag, struct proc *p)
{
	struct octcf_softc *wd;
	struct disklabel *lp;
	int error = 0;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	wd = octcflookup(DISKUNIT(dev));
	if (wd == NULL)
		return ENXIO;

	if ((wd->sc_flags & OCTCFF_LOADED) == 0) {
		error = EIO;
		goto exit;
	}

	switch (xfer) {
	case DIOCRLDINFO:
		lp = malloc(sizeof(*lp), M_TEMP, M_WAITOK);
		octcfgetdisklabel(dev, wd, lp, 0);
		bcopy(lp, wd->sc_dk.dk_label, sizeof(*lp));
		free(lp, M_TEMP, 0);
		goto exit;

	case DIOCGPDINFO:
		octcfgetdisklabel(dev, wd, (struct disklabel *)addr, 1);
		goto exit;

	case DIOCGDINFO:
		*(struct disklabel *)addr = *(wd->sc_dk.dk_label);
		goto exit;

	case DIOCGPART:
		((struct partinfo *)addr)->disklab = wd->sc_dk.dk_label;
		((struct partinfo *)addr)->part =
		    &wd->sc_dk.dk_label->d_partitions[DISKPART(dev)];
		goto exit;

	case DIOCWDINFO:
	case DIOCSDINFO:
		if ((flag & FWRITE) == 0) {
			error = EBADF;
			goto exit;
		}

		if ((error = disk_lock(&wd->sc_dk)) != 0)
			goto exit;

		error = setdisklabel(wd->sc_dk.dk_label,
		    (struct disklabel *)addr, wd->sc_dk.dk_openmask);
		if (error == 0) {
			if (xfer == DIOCWDINFO)
				error = writedisklabel(DISKLABELDEV(dev),
				    octcfstrategy, wd->sc_dk.dk_label);
		}

		disk_unlock(&wd->sc_dk);
		goto exit;

#ifdef notyet
	case DIOCWFORMAT:
		if ((flag & FWRITE) == 0)
			return EBADF;
		{
		struct format_op *fop;
		struct iovec aiov;
		struct uio auio;

		fop = (struct format_op *)addr;
		aiov.iov_base = fop->df_buf;
		aiov.iov_len = fop->df_count;
		auio.uio_iov = &aiov;
		auio.uio_iovcnt = 1;
		auio.uio_resid = fop->df_count;
		auio.uio_segflg = 0;
		auio.uio_offset =
			fop->df_startblk * wd->sc_dk.dk_label->d_secsize;
		auio.uio_procp = p;
		error = physio(wdformat, dev, B_WRITE, minphys, &auio);
		fop->df_count -= auio.uio_resid;
		fop->df_reg[0] = wdc->sc_status;
		fop->df_reg[1] = wdc->sc_error;
		goto exit;
		}
#endif

	default:
		error = ENOTTY;
		goto exit;
	}

#ifdef DIAGNOSTIC
	panic("octcfioctl: impossible");
#endif

 exit:
	device_unref(&wd->sc_dev);
	return (error);
}

#ifdef B_FORMAT
int
wdformat(struct buf *bp)
{
	bp->b_flags |= B_FORMAT;
	return octcfstrategy(bp);
}
#endif

daddr_t
octcfsize(dev_t dev)
{
	struct octcf_softc *wd;
	struct disklabel *lp;
	int part, omask;
	daddr_t size;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	wd = octcflookup(DISKUNIT(dev));
	if (wd == NULL)
		return (-1);

	part = DISKPART(dev);
	omask = wd->sc_dk.dk_openmask & (1 << part);

	if (omask == 0 && octcfopen(dev, 0, S_IFBLK, NULL) != 0) {
		size = -1;
		goto exit;
	}

	lp = wd->sc_dk.dk_label;
	size = DL_SECTOBLK(lp, DL_GETPSIZE(&lp->d_partitions[part]));
	if (omask == 0 && octcfclose(dev, 0, S_IFBLK, NULL) != 0)
		size = -1;

 exit:
	device_unref(&wd->sc_dev);
	return (size);
}

/*
 * Dump core after a system crash.
 */
int
octcfdump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
{
	return ENXIO;
}

int
octcf_read_sectors(struct octcf_softc *wd, uint32_t nr_sectors,
	uint32_t start_sector, void *buf)
{
	uint32_t count;
	uint16_t *ptr = (uint16_t*)buf;
	int error;
	uint8_t status;

	while (nr_sectors--) {
		while ((status = (OCTCF_REG_READ(wd, wdr_status)>>8)) & WDCS_BSY)
			DELAY(OCTCFDELAY);
		octcf_command(wd, start_sector++, WDCC_READ);
		error = octcf_wait_busy(wd);
		if (error != 0)
			return (error);

        	volatile uint16_t dummy;
		for (count = 0; count < SECTOR_SIZE; count+=2) {
			uint16_t temp;
			temp = OCTCF_REG_READ(wd, 0x0);
			*ptr++ = swap16(temp);
			if ((count & 0xf) == 0)
				dummy = OCTCF_REG_READ(wd, wdr_status);
		}
	}
	return (0);
}

int
octcf_write_sectors(struct octcf_softc *wd, uint32_t nr_sectors,
	uint32_t start_sector, void *buf)
{
	uint32_t count;
	uint16_t *ptr = (uint16_t*)buf;
	int error;
	uint8_t status;

	while (nr_sectors--) {
		while ((status = (OCTCF_REG_READ(wd, wdr_status)>>8)) & WDCS_BSY)
			DELAY(OCTCFDELAY);
		octcf_command(wd, start_sector++, WDCC_WRITE);
		if((error = octcf_wait_busy(wd)))
			return (error);

	      	volatile uint16_t dummy;
		for (count = 0; count < SECTOR_SIZE; count+=2) {
			uint16_t temp = *ptr++;
			OCTCF_REG_WRITE(wd, 0x0, swap16(temp));
			if ((count & 0xf) == 0)
				dummy = OCTCF_REG_READ(wd, wdr_status);
		}
	}
	return (0);
}

void
octcf_command(struct octcf_softc *wd, uint32_t lba, uint8_t cmd)
{
	OCTCF_REG_WRITE(wd, wdr_seccnt, 1 | ((lba & 0xff) << 8));
	OCTCF_REG_WRITE(wd, wdr_cyl_lo,
		((lba >> 8) & 0xff) | (((lba >> 16) & 0xff) << 8));
	OCTCF_REG_WRITE(wd, wdr_sdh,
		(((lba >> 24) & 0xff) | 0xe0) | (cmd << 8));
}

int
octcf_wait_busy(struct octcf_softc *wd)
{
	uint8_t status;

	status = OCTCF_REG_READ(wd, wdr_status)>>8;
	while ((status & WDCS_BSY) == WDCS_BSY) {
		if ((status & WDCS_DWF) != 0)
			return (EIO);
		DELAY(OCTCFDELAY);
		status = (uint8_t)(OCTCF_REG_READ(wd, wdr_status)>>8);
	}

	if ((status & WDCS_DRQ) == 0)
		return (ENXIO);

	return (0);
}

/* Get the disk's parameters */
int
octcf_get_params(struct octcf_softc *wd, struct ataparams *params)
{
	char *tb;
	int i;
	u_int16_t *p;
	int count;
	uint8_t status;
	int error;

	OCTCFDEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);

	tb = malloc(ATAPARAMS_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
	if (tb == NULL)
		return CMD_AGAIN;

	while ((status = (OCTCF_REG_READ(wd, wdr_status)>>8)) & WDCS_BSY)
		DELAY(OCTCFDELAY);

	OCTCF_REG_WRITE(wd, wdr_seccnt, 0);
	OCTCF_REG_WRITE(wd, wdr_cyl_lo, 0);
	OCTCF_REG_WRITE(wd, wdr_sdh, 0 | (WDCC_IDENTIFY<<8));

	error = octcf_wait_busy(wd);
	if (error == 0) {
		for (count = 0; count < SECTOR_SIZE; count+=2) {
			uint16_t temp;
			temp = OCTCF_REG_READ(wd, 0x0);

			/* endianess will be swapped below */
			tb[count]   = (temp & 0xff);
			tb[count+1] = (temp & 0xff00)>>8;
		}
	}

	if (error != 0) {
		printf("%s: identify failed: %d\n", __func__, error);
		free(tb, M_DEVBUF, 0);
		return CMD_ERR;
	} else {
		/*
		 * All the fields in the params structure are 16-bit
		 * integers except for the ID strings which are char
		 * strings.  The 16-bit integers are currently in
		 * memory in little-endian, regardless of architecture.
		 * So, they need to be swapped on big-endian architectures
		 * before they are accessed through the ataparams structure.
		 *
		 * The swaps below avoid touching the char strings.
		*/
		swap16_multi((u_int16_t *)tb, 10);
		swap16_multi((u_int16_t *)tb + 20, 3);
		swap16_multi((u_int16_t *)tb + 47, ATAPARAMS_SIZE / 2 - 47);

		/* Read in parameter block. */
		bcopy(tb, params, sizeof(struct ataparams));

		/*
		 * Shuffle string byte order.
		 * ATAPI Mitsumi and NEC drives don't need this.
		 */
		if ((params->atap_config & WDC_CFG_ATAPI_MASK) ==
		    WDC_CFG_ATAPI &&
		    ((params->atap_model[0] == 'N' &&
			params->atap_model[1] == 'E') ||
		     (params->atap_model[0] == 'F' &&
			 params->atap_model[1] == 'X'))) {
			free(tb, M_DEVBUF, 0);
			return CMD_OK;
		}
		for (i = 0; i < sizeof(params->atap_model); i += 2) {
			p = (u_short *)(params->atap_model + i);
			*p = swap16(*p);
		}
		for (i = 0; i < sizeof(params->atap_serial); i += 2) {
			p = (u_short *)(params->atap_serial + i);
			*p = swap16(*p);
		}
		for (i = 0; i < sizeof(params->atap_revision); i += 2) {
			p = (u_short *)(params->atap_revision + i);
			*p = swap16(*p);
		}

		free(tb, M_DEVBUF, 0);
		return CMD_OK;
	}
}


code:
/*	$OpenBSD: octdwctwo.c,v 1.8 2015/07/19 00:33:36 jasper Exp $	*/

/*
 * Copyright (c) 2015 Masao Uebayashi <uebayasi@tombiinc.com>
 *
 * Permission to use, copy, modify, and/or 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/systm.h>
#include <sys/device.h>

#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/octeonreg.h>
#include <machine/octeonvar.h>

#include <octeon/dev/iobusvar.h>
#include <octeon/dev/octhcireg.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>

#include <dev/usb/dwc2/dwc2var.h>
#include <dev/usb/dwc2/dwc2.h>
#include <dev/usb/dwc2/dwc2_core.h>

struct octdwctwo_softc {
	struct dwc2_softc	sc_dwc2;

	/* USBN bus space */
	bus_space_tag_t		sc_bust;
	bus_space_handle_t	sc_regh;
	bus_space_handle_t	sc_regh2;

	void			*sc_ih;
};

int			octdwctwo_match(struct device *, void *, void *);
void			octdwctwo_attach(struct device *, struct device *,
			    void *);
int			octdwctwo_set_dma_addr(void *, dma_addr_t, int);
u_int64_t		octdwctwo_reg2_rd(struct octdwctwo_softc *, bus_size_t);
void			octdwctwo_reg2_wr(struct octdwctwo_softc *, bus_size_t,
			    u_int64_t);
void			octdwctwo_reg_set(struct octdwctwo_softc *, bus_size_t,
			    u_int64_t);
void			octdwctwo_reg_clear(struct octdwctwo_softc *,
			    bus_size_t, u_int64_t);
u_int32_t		octdwctwo_read_4(bus_space_tag_t, bus_space_handle_t,
			    bus_size_t);
void			octdwctwo_write_4(bus_space_tag_t, bus_space_handle_t,
			    bus_size_t, u_int32_t);

const struct cfattach octdwctwo_ca = {
	sizeof(struct octdwctwo_softc), octdwctwo_match, octdwctwo_attach,
};

struct cfdriver dwctwo_cd = {
	NULL, "dwctwo", DV_DULL
};

static struct dwc2_core_params octdwctwo_params = {
	.otg_cap = 2,
	.otg_ver = 0,
	.dma_enable = 1,
	.dma_desc_enable = 0,
	.speed = 0,
	.enable_dynamic_fifo = 1,
	.en_multiple_tx_fifo = 0,
	.host_rx_fifo_size = 256/*XXX*/,
	.host_nperio_tx_fifo_size = 128/*XXX*/,
	.host_perio_tx_fifo_size = 128/*XXX*/,
	.max_transfer_size = 65535,
	.max_packet_count = 511,
	.host_channels = 8,
	.phy_type = 1,
	.phy_utmi_width = 16,
	.phy_ulpi_ddr = 0,
	.phy_ulpi_ext_vbus = 0,
	.i2c_enable = 0,
	.ulpi_fs_ls = 0,
	.host_support_fs_ls_low_power = 0,
	.host_ls_low_power_phy_clk = 0,
	.ts_dline = 0,
	.reload_ctl = 0,
	.ahbcfg = 0x7,
	.uframe_sched = 1,
};

static struct dwc2_core_dma_config octdwctwo_dma_config = {
	.set_dma_addr = octdwctwo_set_dma_addr,
};

/*
 * This bus space tag adjusts register addresses to account for
 * dwc2 using little endian addressing.  dwc2 only does 32bit reads
 * and writes, so only those functions are provided.
 */
bus_space_t octdwctwo_tag = {
	.bus_base = PHYS_TO_XKPHYS(0, CCA_NC),
	.bus_private = NULL,
	._space_read_4 =	octdwctwo_read_4,
	._space_write_4 =	octdwctwo_write_4,
	._space_map =		iobus_space_map,
	._space_unmap =		iobus_space_unmap,
	._space_subregion =	generic_space_region,
	._space_vaddr =		generic_space_vaddr
};

int
octdwctwo_match(struct device *parent, void *match, void *aux)
{
	return (1);
}

void
octdwctwo_attach(struct device *parent, struct device *self, void *aux)
{
	struct octdwctwo_softc *sc = (struct octdwctwo_softc *)self;
	struct iobus_attach_args *aa = aux;
	uint64_t clk;
	int rc;

	sc->sc_dwc2.sc_iot = &octdwctwo_tag;
	sc->sc_dwc2.sc_bus.pipe_size = sizeof(struct usbd_pipe);
	sc->sc_dwc2.sc_bus.dmatag = aa->aa_dmat;
	sc->sc_dwc2.sc_params = &octdwctwo_params;

	rc = bus_space_map(sc->sc_dwc2.sc_iot, USBC_BASE, USBC_SIZE,
	    0, &sc->sc_dwc2.sc_ioh);
	KASSERT(rc == 0);

	sc->sc_bust = aa->aa_bust;
	rc = bus_space_map(sc->sc_bust, USBN_BASE, USBN_SIZE,
	    0, &sc->sc_regh);
	KASSERT(rc == 0);
	rc = bus_space_map(sc->sc_bust, USBN_2_BASE, USBN_2_SIZE,
	    0, &sc->sc_regh2);
	KASSERT(rc == 0);

	/*
	 * Clock setup.
	 */
	clk = bus_space_read_8(sc->sc_bust, sc->sc_regh, USBN_CLK_CTL_OFFSET);
	clk |= USBN_CLK_CTL_POR;
	clk &= ~(USBN_CLK_CTL_HRST | USBN_CLK_CTL_PRST | USBN_CLK_CTL_HCLK_RST |
	    USBN_CLK_CTL_ENABLE | USBN_CLK_CTL_P_C_SEL | USBN_CLK_CTL_P_RTYPE);
	clk |= SET_USBN_CLK_CTL_DIVIDE(0x4ULL)
	    | SET_USBN_CLK_CTL_DIVIDE2(0x0ULL);

	bus_space_write_8(sc->sc_bust, sc->sc_regh, USBN_CLK_CTL_OFFSET, clk);
	bus_space_read_8(sc->sc_bust, sc->sc_regh, USBN_CLK_CTL_OFFSET);

	/*
	 * Reset HCLK and wait for it to stabilize.
	 */
	octdwctwo_reg_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_HCLK_RST);
	delay(64);

	octdwctwo_reg_clear(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_POR);

	/*
	 * Wait for the PHY clock to start.
	 */
	delay(1000);

	octdwctwo_reg_set(sc, USBN_USBP_CTL_STATUS_OFFSET,
	    USBN_USBP_CTL_STATUS_ATE_RESET);
	delay(10);

	octdwctwo_reg_clear(sc, USBN_USBP_CTL_STATUS_OFFSET,
			USBN_USBP_CTL_STATUS_ATE_RESET);
	octdwctwo_reg_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_PRST);

	/*
	 * Select host mode.
	 */
	octdwctwo_reg_clear(sc, USBN_USBP_CTL_STATUS_OFFSET,
	    USBN_USBP_CTL_STATUS_HST_MODE);
	delay(1);

	octdwctwo_reg_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_HRST);

	/*
	 * Enable clock.
	 */
	octdwctwo_reg_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_ENABLE);
	delay(1);

	strlcpy(sc->sc_dwc2.sc_vendor, "Octeon", sizeof(sc->sc_dwc2.sc_vendor));

	rc = dwc2_init(&sc->sc_dwc2);
	if (rc != 0)
		return;
	octdwctwo_dma_config.set_dma_addr_data = sc;
	rc = dwc2_dma_config(&sc->sc_dwc2, &octdwctwo_dma_config);
	if (rc != 0)
		return;

	printf("\n");

	sc->sc_dwc2.sc_child = config_found(&sc->sc_dwc2.sc_bus.bdev,
	    &sc->sc_dwc2.sc_bus, usbctlprint);

	sc->sc_ih = octeon_intr_establish(CIU_INT_USB, IPL_USB, dwc2_intr,
	    (void *)&sc->sc_dwc2, sc->sc_dwc2.sc_bus.bdev.dv_xname);
	KASSERT(sc->sc_ih != NULL);
}

int
octdwctwo_set_dma_addr(void *data, dma_addr_t dma_addr, int ch)
{
	struct octdwctwo_softc *sc = data;

	octdwctwo_reg2_wr(sc,
	    USBN_DMA0_INB_CHN0_OFFSET + ch * 0x8, dma_addr);
	octdwctwo_reg2_wr(sc,
	    USBN_DMA0_OUTB_CHN0_OFFSET + ch * 0x8, dma_addr);
	return 0;
}

u_int64_t
octdwctwo_reg2_rd(struct octdwctwo_softc *sc, bus_size_t offset)
{
	u_int64_t value;

	value = bus_space_read_8(sc->sc_bust, sc->sc_regh2, offset);
	return value;
}

void
octdwctwo_reg2_wr(struct octdwctwo_softc *sc, bus_size_t offset, u_int64_t value)
{
	bus_space_write_8(sc->sc_bust, sc->sc_regh2, offset, value);
	/* guarantee completion of the store operation on RSL registers*/
	bus_space_read_8(sc->sc_bust, sc->sc_regh2, offset);
}

void
octdwctwo_reg_set(struct octdwctwo_softc *sc, bus_size_t offset,
    u_int64_t bits)
{
	u_int64_t value;
	value = bus_space_read_8(sc->sc_bust, sc->sc_regh, offset);
	value |= bits;

	bus_space_write_8(sc->sc_bust, sc->sc_regh, offset, value);
	bus_space_read_8(sc->sc_bust, sc->sc_regh, offset);
}

void
octdwctwo_reg_clear(struct octdwctwo_softc *sc, bus_size_t offset,
    u_int64_t bits)
{
	u_int64_t value;
	value = bus_space_read_8(sc->sc_bust, sc->sc_regh, offset);
	value &= ~bits;

	bus_space_write_8(sc->sc_bust, sc->sc_regh, offset, value);
	bus_space_read_8(sc->sc_bust, sc->sc_regh, offset);
}

u_int32_t
octdwctwo_read_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	return *(volatile u_int32_t *)(h + (o^4));
}

void
octdwctwo_write_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o,
    u_int32_t v)
{
	*(volatile u_int32_t *)(h + (o^4)) = v;
}


code:
/*	$OpenBSD: octeon_bus_space.c,v 1.1 2010/10/28 22:52:10 syuu Exp $ */

/*
 * Copyright (c) 2001-2003 Opsycon AB  (www.opsycon.se / www.opsycon.com)
 *
 * 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.
 *
 */

/*
 * Simple generic bus access primitives.
 */

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

#include <machine/bus.h>

uint8_t
generic_space_read_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	return *(volatile uint8_t *)(h + o);
}

uint16_t
generic_space_read_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	return *(volatile uint16_t *)(h + o);
}

uint32_t
generic_space_read_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	return *(volatile uint32_t *)(h + o);
}

uint64_t
generic_space_read_8(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	return *(volatile uint64_t *)(h + o);
}

void
generic_space_write_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o,
    uint8_t v)
{
	*(volatile uint8_t *)(h + o) = v;
}

void
generic_space_write_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o,
    uint16_t v)
{
	*(volatile uint16_t *)(h + o) = v;
}

void
generic_space_write_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o,
    uint32_t v)
{
	*(volatile uint32_t *)(h + o) = v;
}

void
generic_space_write_8(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o,
    uint64_t v)
{
	*(volatile uint64_t *)(h + o) = v;
}

void
generic_space_read_raw_2(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    uint8_t *buf, bus_size_t len)
{
	volatile uint16_t *addr = (volatile uint16_t *)(h + o);
	len >>= 1;
	while (len-- != 0) {
		*(uint16_t *)buf = *addr;
		buf += 2;
	}
}

void
generic_space_write_raw_2(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const uint8_t *buf, bus_size_t len)
{
	volatile uint16_t *addr = (volatile uint16_t *)(h + o);
	len >>= 1;
	while (len-- != 0) {
		*addr = *(uint16_t *)buf;
		buf += 2;
	}
}

void
generic_space_read_raw_4(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    uint8_t *buf, bus_size_t len)
{
	volatile uint32_t *addr = (volatile uint32_t *)(h + o);
	len >>= 2;
	while (len-- != 0) {
		*(uint32_t *)buf = *addr;
		buf += 4;
	}
}

void
generic_space_write_raw_4(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const uint8_t *buf, bus_size_t len)
{
	volatile uint32_t *addr = (volatile uint32_t *)(h + o);
	len >>= 2;
	while (len-- != 0) {
		*addr = *(uint32_t *)buf;
		buf += 4;
	}
}

void
generic_space_read_raw_8(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    uint8_t *buf, bus_size_t len)
{
	volatile uint64_t *addr = (volatile uint64_t *)(h + o);
	len >>= 3;
	while (len-- != 0) {
		*(uint64_t *)buf = *addr;
		buf += 8;
	}
}

void
generic_space_write_raw_8(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const uint8_t *buf, bus_size_t len)
{
	volatile uint64_t *addr = (volatile uint64_t *)(h + o);
	len >>= 3;
	while (len-- != 0) {
		*addr = *(uint64_t *)buf;
		buf += 8;
	}
}

int
generic_space_map(bus_space_tag_t t, bus_addr_t offs, bus_size_t size,
    int flags, bus_space_handle_t *bshp)
{
	*bshp = t->bus_base + offs;
	return 0;
}

void
generic_space_unmap(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t size)
{
}

int
generic_space_region(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, bus_size_t size, bus_space_handle_t *nbshp)
{
	*nbshp = bsh + offset;
	return 0;
}

void *
generic_space_vaddr(bus_space_tag_t t, bus_space_handle_t h)
{
	return (void *)h;
}


code:
/*
 * Copyright (c) 2000-2004 Opsycon AB  (www.opsycon.se)
 *
 * 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.
 *
 */

/*
 * Interrupt support for Octeon Processor.
 */

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

#include <mips64/mips_cpu.h>

#include <machine/autoconf.h>
#include <machine/intr.h>
#include <machine/octeonreg.h>

#include <octeon/dev/iobusvar.h>

extern bus_space_handle_t iobus_h;

#define OCTEON_NINTS 64

void	 octeon_intr_makemasks(void);
void	 octeon_splx(int);
uint32_t octeon_iointr(uint32_t, struct trap_frame *);
uint32_t octeon_aux(uint32_t, struct trap_frame *);
int	 octeon_iointr_skip(struct intrhand *, uint64_t, uint64_t);
void	 octeon_setintrmask(int);

struct intrhand *octeon_intrhand[OCTEON_NINTS];

#define	INTPRI_CIU_0	(INTPRI_CLOCK + 1)

uint64_t octeon_intem[MAXCPUS];
uint64_t octeon_imask[MAXCPUS][NIPLS];

void
octeon_intr_init(void)
{
	int cpuid = cpu_number();
	bus_space_write_8(&iobus_tag, iobus_h, CIU_IP2_EN0(cpuid), 0);
	bus_space_write_8(&iobus_tag, iobus_h, CIU_IP3_EN0(cpuid), 0);
	bus_space_write_8(&iobus_tag, iobus_h, CIU_IP2_EN1(cpuid), 0);
	bus_space_write_8(&iobus_tag, iobus_h, CIU_IP3_EN1(cpuid), 0);

	set_intr(INTPRI_CIU_0, CR_INT_0, octeon_iointr);
	register_splx_handler(octeon_splx);
}

/*
 * Establish an interrupt handler called from the dispatcher.
 * The interrupt function established should return zero if there was nothing
 * to serve (no int) and non-zero when an interrupt was serviced.
 *
 * Interrupts are numbered from 1 and up where 1 maps to HW int 0.
 * XXX There is no reason to keep this... except for hardcoded interrupts
 * XXX in kernel configuration files...
 */
void *
octeon_intr_establish(int irq, int level,
    int (*ih_fun)(void *), void *ih_arg, const char *ih_what)
{
	int cpuid = cpu_number();
	struct intrhand **p, *q, *ih;
	int s;

#ifdef DIAGNOSTIC
	if (irq >= OCTEON_NINTS || irq < 0)
		panic("intr_establish: illegal irq %d", irq);
#endif

	ih = malloc(sizeof *ih, M_DEVBUF, M_NOWAIT);
	if (ih == NULL)
		return NULL;

	ih->ih_next = NULL;
	ih->ih_fun = ih_fun;
	ih->ih_arg = ih_arg;
	ih->ih_level = level;
	ih->ih_irq = irq;
	evcount_attach(&ih->ih_count, ih_what, (void *)&ih->ih_irq);

	s = splhigh();

	/*
	 * Figure out where to put the handler.
	 * This is O(N^2), but we want to preserve the order, and N is
	 * generally small.
	 */
	for (p = &octeon_intrhand[irq]; (q = *p) != NULL;
	    p = (struct intrhand **)&q->ih_next)
		;
	*p = ih;

	octeon_intem[cpuid] |= 1UL << irq;
	octeon_intr_makemasks();

	splx(s);	/* causes hw mask update */

	return (ih);
}

void
octeon_intr_disestablish(void *ih)
{
	/* XXX */
	panic("%s not implemented", __func__);
}

void
octeon_splx(int newipl)
{
	struct cpu_info *ci = curcpu();

	/* Update masks to new ipl. Order highly important! */
	__asm__ (".set noreorder\n");
	ci->ci_ipl = newipl;
	mips_sync();
	__asm__ (".set reorder\n");
	if (CPU_IS_PRIMARY(ci))
		octeon_setintrmask(newipl);
	/* If we still have softints pending trigger processing. */
	if (ci->ci_softpending != 0 && newipl < IPL_SOFTINT)
		setsoftintr0();
}

/*
 * Recompute interrupt masks.
 */
void
octeon_intr_makemasks()
{
	int cpuid = cpu_number();
	int irq, level;
	struct intrhand *q;
	uint intrlevel[OCTEON_NINTS];

	/* First, figure out which levels each IRQ uses. */
	for (irq = 0; irq < OCTEON_NINTS; irq++) {
		uint levels = 0;
		for (q = (struct intrhand *)octeon_intrhand[irq]; q != NULL;
			q = q->ih_next)
			levels |= 1 << q->ih_level;
		intrlevel[irq] = levels;
	}

	/*
	 * Then figure out which IRQs use each level.
	 * Note that we make sure never to overwrite imask[IPL_HIGH], in
	 * case an interrupt occurs during intr_disestablish() and causes
	 * an unfortunate splx() while we are here recomputing the masks.
	 */
	for (level = IPL_NONE; level < NIPLS; level++) {
		uint64_t irqs = 0;
		for (irq = 0; irq < OCTEON_NINTS; irq++)
			if (intrlevel[irq] & (1 << level))
				irqs |= 1UL << irq;
		octeon_imask[cpuid][level] = irqs;
	}
	/*
	 * There are tty, network and disk drivers that use free() at interrupt
	 * time, so vm > (tty | net | bio).
	 *
	 * Enforce a hierarchy that gives slow devices a better chance at not
	 * dropping data.
	 */
	octeon_imask[cpuid][IPL_NET] |= octeon_imask[cpuid][IPL_BIO];
	octeon_imask[cpuid][IPL_TTY] |= octeon_imask[cpuid][IPL_NET];
	octeon_imask[cpuid][IPL_VM] |= octeon_imask[cpuid][IPL_TTY];
	octeon_imask[cpuid][IPL_CLOCK] |= octeon_imask[cpuid][IPL_VM];
	octeon_imask[cpuid][IPL_HIGH] |= octeon_imask[cpuid][IPL_CLOCK];
	octeon_imask[cpuid][IPL_IPI] |= octeon_imask[cpuid][IPL_HIGH];

	/*
	 * These are pseudo-levels.
	 */
	octeon_imask[cpuid][IPL_NONE] = 0;
}

/*
 * Interrupt dispatcher.
 */
uint32_t
octeon_iointr(uint32_t hwpend, struct trap_frame *frame)
{
	struct cpu_info *ci = curcpu();
	int cpuid = cpu_number();
	uint64_t imr, isr, mask;
	int ipl;
	int bit;
	struct intrhand *ih;
	int rc;
	uint64_t sum0 = CIU_IP2_SUM0(cpuid);
	uint64_t en0 = CIU_IP2_EN0(cpuid);

	isr = bus_space_read_8(&iobus_tag, iobus_h, sum0);
	imr = bus_space_read_8(&iobus_tag, iobus_h, en0);
	bit = 63;

	isr &= imr;
	if (isr == 0)
		return 0;	/* not for us */

	/*
	 * Mask all pending interrupts.
	 */
	bus_space_write_8(&iobus_tag, iobus_h, en0, imr & ~isr);

	/*
	 * If interrupts are spl-masked, mask them and wait for splx()
	 * to reenable them when necessary.
	 */
	if ((mask = isr & octeon_imask[cpuid][frame->ipl]) != 0) {
		isr &= ~mask;
		imr &= ~mask;
	}

	/*
	 * Now process allowed interrupts.
	 */
	if (isr != 0) {
		int lvl, bitno;
		uint64_t tmpisr;

		__asm__ (".set noreorder\n");
		ipl = ci->ci_ipl;
		mips_sync();
		__asm__ (".set reorder\n");

		/* Service higher level interrupts first */
		for (lvl = NIPLS - 1; lvl != IPL_NONE; lvl--) {
			tmpisr = isr & (octeon_imask[cpuid][lvl] ^ octeon_imask[cpuid][lvl - 1]);
			if (tmpisr == 0)
				continue;
			for (bitno = bit, mask = 1UL << bitno; mask != 0;
			    bitno--, mask >>= 1) {
				if ((tmpisr & mask) == 0)
					continue;

				rc = 0;
				for (ih = (struct intrhand *)octeon_intrhand[bitno];
					ih != NULL;
				    ih = ih->ih_next) {
#ifdef MULTIPROCESSOR
					register_t sr;
#endif
					splraise(ih->ih_level);
#ifdef MULTIPROCESSOR
					if (ih->ih_level < IPL_IPI) {
						sr = getsr();
						ENABLEIPI();
						if (ih->ih_level < IPL_CLOCK)
							__mp_lock(&kernel_lock);
					}
#endif
					if ((*ih->ih_fun)(ih->ih_arg) != 0) {
						rc = 1;
						atomic_inc_long((unsigned long *)
						    &ih->ih_count.ec_count);
					}
#ifdef MULTIPROCESSOR
					if (ih->ih_level < IPL_IPI) {
						if (ih->ih_level < IPL_CLOCK)
							__mp_unlock(&kernel_lock);
						setsr(sr);
					}
#endif
					__asm__ (".set noreorder\n");
					ci->ci_ipl = ipl;
					mips_sync();
					__asm__ (".set reorder\n");
				}
				if (rc == 0)
					printf("spurious interrupt %d\n", bitno);

				isr ^= mask;
				if ((tmpisr ^= mask) == 0)
					break;
			}
		}

		/*
		 * Reenable interrupts which have been serviced.
		 */
		bus_space_write_8(&iobus_tag, iobus_h, en0, imr);
	}

	return hwpend;
}

void
octeon_setintrmask(int level)
{
	int cpuid = cpu_number();

	bus_space_write_8(&iobus_tag, iobus_h, CIU_IP2_EN0(cpuid),
		octeon_intem[cpuid] & ~octeon_imask[cpuid][level]);
}


code:
/*	$OpenBSD: octeon_iobus.c,v 1.14 2015/07/20 19:44:32 pirofti Exp $ */

/*
 * Copyright (c) 2000-2004 Opsycon AB  (www.opsycon.se)
 *
 * 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.
 *
 */

/*
 * This is a iobus driver.
 * It handles configuration of all devices on the processor bus except UART.
 */

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

#include <machine/autoconf.h>
#include <machine/intr.h>
#include <machine/octeonvar.h>
#include <machine/octeonreg.h>

#include <octeon/dev/iobusvar.h>
#include <octeon/dev/cn30xxgmxreg.h>
#include <octeon/dev/octhcireg.h>	/* USBN_BASE */

int	iobusmatch(struct device *, void *, void *);
void	iobusattach(struct device *, struct device *, void *);
int	iobusprint(void *, const char *);
int	iobussubmatch(struct device *, void *, void *);
int	iobussearch(struct device *, void *, void *);

u_int8_t iobus_read_1(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int16_t iobus_read_2(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int32_t iobus_read_4(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int64_t iobus_read_8(bus_space_tag_t, bus_space_handle_t, bus_size_t);

void	 iobus_write_1(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int8_t);
void	 iobus_write_2(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int16_t);
void	 iobus_write_4(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int32_t);
void	 iobus_write_8(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int64_t);

void	 iobus_read_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 iobus_read_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 iobus_read_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);

bus_addr_t iobus_pa_to_device(paddr_t);
paddr_t	 iobus_device_to_pa(bus_addr_t);

struct cfattach iobus_ca = {
	sizeof(struct device), iobusmatch, iobusattach
};

struct cfdriver iobus_cd = {
	NULL, "iobus", DV_DULL
};

bus_space_t iobus_tag = {
	.bus_base = PHYS_TO_XKPHYS(0, CCA_NC),
	.bus_private = NULL,
	._space_read_1 =	generic_space_read_1,
	._space_write_1 =	generic_space_write_1,
	._space_read_2 =	generic_space_read_2,
	._space_write_2 =	generic_space_write_2,
	._space_read_4 =	generic_space_read_4,
	._space_write_4 =	generic_space_write_4,
	._space_read_8 =	generic_space_read_8,
	._space_write_8 =	generic_space_write_8,
	._space_read_raw_2 =	generic_space_read_raw_2,
	._space_write_raw_2 =	generic_space_write_raw_2,
	._space_read_raw_4 =	generic_space_read_raw_4,
	._space_write_raw_4 =	generic_space_write_raw_4,
	._space_read_raw_8 =	generic_space_read_raw_8,
	._space_write_raw_8 =	generic_space_write_raw_8,
	._space_map =		iobus_space_map,
	._space_unmap =		iobus_space_unmap,
	._space_subregion =	generic_space_region,
	._space_vaddr =		generic_space_vaddr
};

bus_space_handle_t iobus_h;

struct machine_bus_dma_tag iobus_bus_dma_tag = {
	NULL,			/* _cookie */
	_dmamap_create,
	_dmamap_destroy,
	_dmamap_load,
	_dmamap_load_mbuf,
	_dmamap_load_uio,
	_dmamap_load_raw,
	_dmamap_load_buffer,
	_dmamap_unload,
	_dmamap_sync,
	_dmamem_alloc,
	_dmamem_free,
	_dmamem_map,
	_dmamem_unmap,
	_dmamem_mmap,
	iobus_pa_to_device,
	iobus_device_to_pa,
	0
};

/*
 * List of iobus child devices whose base addresses are too large to be
 * recorded in the kernel configuration file. So look them up from here instead.
 */

static const struct octeon_iobus_addrs iobus_addrs[] = {
	{ "octcf",	OCTEON_CF_BASE  },
	{ "cn30xxgmx",	GMX0_BASE_PORT0 },
	{ "octrng",	OCTEON_RNG_BASE },
	{ "dwctwo",	USBN_BASE       },
	{ "amdcf",	OCTEON_AMDCF_BASE},
};

/* There can only be one. */
int	iobus_found;

/*
 * Match bus only to targets which have this bus.
 */
int
iobusmatch(struct device *parent, void *match, void *aux)
{
	if (iobus_found)
		return (0);

	return (1);
}

int
iobusprint(void *aux, const char *iobus)
{
	struct iobus_attach_args *aa = aux;

	if (iobus != NULL)
		printf("%s at %s", aa->aa_name, iobus);

	if (aa->aa_addr != 0)
		printf(" base 0x%lx", aa->aa_addr);
	if (aa->aa_irq >= 0)
		printf(" irq %d", aa->aa_irq);

	return (UNCONF);
}

int
iobussubmatch(struct device *parent, void *vcf, void *args)
{
	struct cfdata *cf = vcf;
	struct iobus_attach_args *aa = args;

	if (strcmp(cf->cf_driver->cd_name, aa->aa_name) != 0)
		return 0;

	if (cf->cf_loc[0] != -1 && cf->cf_loc[0] != (int)aa->aa_addr)
		return 0;

	return (*cf->cf_attach->ca_match)(parent, cf, aa);
}

void
iobusattach(struct device *parent, struct device *self, void *aux)
{
	struct device *sc = self;
	struct octeon_config oc;

	/*
	 * Map and setup CIU control registers.
	 */
	if (bus_space_map(&iobus_tag, OCTEON_CIU_BASE, OCTEON_CIU_SIZE, 0,
		&iobus_h)) {
		printf(": can't map CIU control registers\n");
		return;
	}

	iobus_found = 1;

	printf("\n");

	octeon_intr_init();

	/* XXX */
	oc.mc_iobus_bust = &iobus_tag;
	oc.mc_iobus_dmat = &iobus_bus_dma_tag;
	void	cn30xxfpa_bootstrap(struct octeon_config *);
	cn30xxfpa_bootstrap(&oc);
	void	cn30xxpow_bootstrap(struct octeon_config *);
	cn30xxpow_bootstrap(&oc);

	/*
	 * Attach all subdevices as described in the config file.
	 */
	config_search(iobussearch, self, sc);
}

int
iobussearch(struct device *parent, void *v, void *aux)
{
	struct iobus_attach_args aa;
	struct cfdata *cf = v;
	int i;

	aa.aa_name = cf->cf_driver->cd_name;
	aa.aa_bust = &iobus_tag;
	aa.aa_dmat = &iobus_bus_dma_tag;
	aa.aa_addr = cf->cf_loc[0];
	aa.aa_irq  = cf->cf_loc[1];
	aa.aa_unitno = cf->cf_unit;

	/* No address specified, try to look it up. */
	if (aa.aa_addr == -1) {
		for (i = 0; i < nitems(iobus_addrs); i++) {
			if (strcmp(iobus_addrs[i].name, cf->cf_driver->cd_name) == 0)
				aa.aa_addr = iobus_addrs[i].address;
		}
	}

	if (cf->cf_attach->ca_match(parent, cf, &aa) == 0)
		return 0;

	config_attach(parent, cf, &aa, iobusprint);
	return 1;
}

int
iobus_space_map(bus_space_tag_t t, bus_addr_t offs, bus_size_t size,
    int flags, bus_space_handle_t *bshp)
{
	if (ISSET(flags, BUS_SPACE_MAP_KSEG0)) {
		*bshp = PHYS_TO_CKSEG0(offs);
		return 0;
	}
	if (ISSET(flags, BUS_SPACE_MAP_CACHEABLE))
		offs +=
		    PHYS_TO_XKPHYS(0, CCA_CACHED) - PHYS_TO_XKPHYS(0, CCA_NC);
	*bshp = t->bus_base + offs;
	return 0;
}

void
iobus_space_unmap(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t size)
{
}

/*
 * Iobus bus_dma helpers.
 */

bus_addr_t
iobus_pa_to_device(paddr_t pa)
{
	return (bus_addr_t)pa;
}

paddr_t
iobus_device_to_pa(bus_addr_t addr)
{
	return (paddr_t)addr;
}