/*	$NetBSD: mvxpbm.c,v 1.4 2024/02/08 20:51:24 andvar Exp $	*/
/*
 * Copyright (c) 2015 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 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mvxpbm.c,v 1.4 2024/02/08 20:51:24 andvar Exp $");

#include "opt_multiprocessor.h"

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

#include <dev/marvell/marvellreg.h>
#include <dev/marvell/marvellvar.h>

#include "mvxpbmvar.h"

#ifdef DEBUG
#define STATIC /* nothing */
#define DPRINTF(fmt, ...) \
	do { \
		if (mvxpbm_debug >= 1) { \
			printf("%s: ", __func__); \
			printf((fmt), ##__VA_ARGS__); \
		} \
	} while (/*CONSTCOND*/0)
#define DPRINTFN(level , fmt, ...) \
	do { \
		if (mvxpbm_debug >= (level)) { \
			printf("%s: ", __func__); \
			printf((fmt), ##__VA_ARGS__); \
		} \
	} while (/*CONSTCOND*/0)
#define DPRINTDEV(dev, level, fmt, ...) \
	do { \
		if (mvxpbm_debug >= (level)) { \
			device_printf((dev), \
			    "%s: "fmt , __func__, ##__VA_ARGS__); \
		} \
	} while (/*CONSTCOND*/0)
#define DPRINTSC(sc, level, fmt, ...) \
	do { \
		device_t dev = (sc)->sc_dev; \
		if (mvxpbm_debug >= (level)) { \
			device_printf(dev, \
			    "%s: " fmt, __func__, ##__VA_ARGS__); \
		} \
	} while (/*CONSTCOND*/0)
#else
#define STATIC static
#define DPRINTF(fmt, ...)
#define DPRINTFN(level, fmt, ...)
#define DPRINTDEV(dev, level, fmt, ...)
#define DPRINTSC(sc, level, fmt, ...)
#endif

/* autoconf(9) */
STATIC int mvxpbm_match(device_t, cfdata_t, void *);
STATIC void mvxpbm_attach(device_t, device_t, void *);
STATIC int mvxpbm_evcnt_attach(struct mvxpbm_softc *);
CFATTACH_DECL_NEW(mvxpbm_mbus, sizeof(struct mvxpbm_softc),
    mvxpbm_match, mvxpbm_attach, NULL, NULL);

/* DMA buffers */
STATIC int mvxpbm_alloc_buffer(struct mvxpbm_softc *);

/* mbuf subroutines */
STATIC void mvxpbm_free_mbuf(struct mbuf *, void *, size_t, void *);

/* singleton device instance */
static struct mvxpbm_softc sc_emul;
static struct mvxpbm_softc *sc0;

/* debug level */
#ifdef DEBUG
static int mvxpbm_debug = 0;
#endif

/*
 * autoconf(9)
 */
STATIC int
mvxpbm_match(device_t parent, cfdata_t match, void *aux)
{
	struct marvell_attach_args *mva = aux;

	if (strcmp(mva->mva_name, match->cf_name) != 0)
		return 0;
	if (mva->mva_unit > MVXPBM_UNIT_MAX)
		return 0;
	if (sc0 != NULL)
		return 0;
	if (mva->mva_offset != MVA_OFFSET_DEFAULT) {
		/* Hardware BM is not supported yet. */
		return 0;
	}

	return 1;
}

STATIC void
mvxpbm_attach(device_t parnet, device_t self, void *aux)
{
	struct marvell_attach_args *mva = aux;
	struct mvxpbm_softc *sc = device_private(self);

	aprint_naive("\n");
	aprint_normal(": Marvell ARMADA Buffer Manager\n");
	memset(sc, 0, sizeof(*sc));
	sc->sc_dev = self;
	sc->sc_iot = mva->mva_iot;
	sc->sc_dmat = mva->mva_dmat;

	if (mva->mva_offset == MVA_OFFSET_DEFAULT) { 
		aprint_normal_dev(sc->sc_dev, "Software emulation.\n");
		sc->sc_emul = 1;
	}

	mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_NET);
	LIST_INIT(&sc->sc_free);
	LIST_INIT(&sc->sc_inuse);

	/* DMA buffers */
	if (mvxpbm_alloc_buffer(sc) != 0)
		return;

	/* event counters */
	mvxpbm_evcnt_attach(sc);

	sc0 = sc;
	return;

}

STATIC int
mvxpbm_evcnt_attach(struct mvxpbm_softc *sc)
{
	return 0;
}

/*
 * DMA buffers
 */
STATIC int
mvxpbm_alloc_buffer(struct mvxpbm_softc *sc)
{
	bus_dma_segment_t segs;
	char *kva, *ptr, *ptr_next, *ptr_data;
	char *bm_buf_end;
	uint32_t align, pad;
	int nsegs;
	int error;

	/*
	 * set default buffer sizes. this will changed to satisfy 
	 * alignment restrictions.
	 */
	sc->sc_chunk_count = 0;
	sc->sc_chunk_size = MVXPBM_PACKET_SIZE;
	sc->sc_chunk_header_size = sizeof(struct mvxpbm_chunk);
	sc->sc_chunk_packet_offset = 64;

	/*
	 * adjust bm_chunk_size, bm_chunk_header_size, bm_slotsize
	 * to satisfy alignment restrictions. 
	 *
	 *    <----------------  bm_slotsize [oct.] ------------------>
	 *                               <--- bm_chunk_size[oct.] ---->
	 *    <--- header_size[oct] ---> <-- MBXPE_BM_SIZE[oct.] --->
	 *   +-----------------+--------+---------+-----------------+--+
	 *   | bm_chunk hdr    |pad     |pkt_off  |   packet data   |  |
	 *   +-----------------+--------+---------+-----------------+--+
	 *   ^                          ^         ^                    ^
	 *   |                          |         |                    |
	 *   ptr                 ptr_data  DMA here         ptr_next
	 *
	 * Restrictions:
	 *   - total buffer size must be multiple of MVXPBM_BUF_ALIGN
	 *   - ptr must be aligned to MVXPBM_CHUNK_ALIGN
	 *   - ptr_data must be aligned to MVXPEBM_DATA_ALIGN
	 *   - bm_chunk_size must be multiple of 8[bytes].
	 */
	/* start calclation from  0x0000.0000 */
	ptr = (char *)0;

	/* align start of packet data */
	ptr_data = ptr + sc->sc_chunk_header_size;
	align = (unsigned long)ptr_data & MVXPBM_DATA_MASK;
	if (align != 0) {
		pad = MVXPBM_DATA_ALIGN - align;
		sc->sc_chunk_header_size += pad;
		DPRINTSC(sc, 1, "added padding to BM header, %u bytes\n", pad);
	}

	/* align size of packet data */
	ptr_data = ptr + sc->sc_chunk_header_size;
	ptr_next = ptr_data + MVXPBM_PACKET_SIZE;
	align = (unsigned long)ptr_next & MVXPBM_CHUNK_MASK;
	if (align != 0) {
		pad = MVXPBM_CHUNK_ALIGN - align;
		ptr_next += pad;
		DPRINTSC(sc, 1, "added padding to BM pktbuf, %u bytes\n", pad);
	}
	sc->sc_slotsize = ptr_next - ptr;
	sc->sc_chunk_size = ptr_next - ptr_data;
	KASSERT((sc->sc_chunk_size % MVXPBM_DATA_UNIT) == 0);

	/* align total buffer size to Mbus window boundary */
	sc->sc_buf_size = sc->sc_slotsize * MVXPBM_NUM_SLOTS;
	align = (unsigned long)sc->sc_buf_size & MVXPBM_BUF_MASK;
	if (align != 0) {
		pad = MVXPBM_BUF_ALIGN - align;
		sc->sc_buf_size += pad;
		DPRINTSC(sc, 1,
		    "expand buffer to fit page boundary, %u bytes\n", pad);
	}

	/*
	 * get the aligned buffer from busdma(9) framework
	 */
	if (bus_dmamem_alloc(sc->sc_dmat, sc->sc_buf_size, MVXPBM_BUF_ALIGN, 0,
	    &segs, 1, &nsegs, BUS_DMA_NOWAIT)) {
		aprint_error_dev(sc->sc_dev, "can't alloc BM buffers\n");
		return ENOBUFS;
	}
	if (bus_dmamem_map(sc->sc_dmat, &segs, nsegs, sc->sc_buf_size,
	    (void **)&kva, BUS_DMA_NOWAIT)) {
		aprint_error_dev(sc->sc_dev,
		    "can't map dma buffers (%zu bytes)\n", sc->sc_buf_size);
		error = ENOBUFS;
		goto fail1;
	}
	if (bus_dmamap_create(sc->sc_dmat, sc->sc_buf_size, 1, sc->sc_buf_size,
	    0, BUS_DMA_NOWAIT, &sc->sc_buf_map)) {
		aprint_error_dev(sc->sc_dev, "can't create dma map\n");
		error = ENOBUFS;
		goto fail2;
	}
	if (bus_dmamap_load(sc->sc_dmat, sc->sc_buf_map,
	    kva, sc->sc_buf_size, NULL, BUS_DMA_NOWAIT)) {
		aprint_error_dev(sc->sc_dev, "can't load dma map\n");
		error = ENOBUFS;
		goto fail3;
	}
	sc->sc_buf = (void *)kva;
	sc->sc_buf_pa = segs.ds_addr;
	bm_buf_end = (void *)(kva + sc->sc_buf_size);
	DPRINTSC(sc, 1, "memory pool at %p\n", sc->sc_buf);

	/* slice the buffer */
	mvxpbm_lock(sc);
	for (ptr = sc->sc_buf; ptr + sc->sc_slotsize <= bm_buf_end;
	    ptr += sc->sc_slotsize) {
		struct mvxpbm_chunk *chunk;

		/* initialize chunk */
		ptr_data = ptr + sc->sc_chunk_header_size;
		chunk = (struct mvxpbm_chunk *)ptr;
		chunk->m = NULL;
		chunk->sc = sc;
		chunk->off = (ptr - sc->sc_buf);
		chunk->pa = (paddr_t)(sc->sc_buf_pa + chunk->off);
		chunk->buf_off = (ptr_data - sc->sc_buf);
		chunk->buf_pa = (paddr_t)(sc->sc_buf_pa + chunk->buf_off);
		chunk->buf_va = (vaddr_t)(sc->sc_buf + chunk->buf_off);
		chunk->buf_size = sc->sc_chunk_size;

		/* add to free list (for software management) */
		LIST_INSERT_HEAD(&sc->sc_free, chunk, link);
		mvxpbm_dmamap_sync(chunk, BM_SYNC_ALL, BUS_DMASYNC_PREREAD);
		sc->sc_chunk_count++;

		DPRINTSC(sc, 9, "new chunk %p\n", (void *)chunk->buf_va);
	}
	mvxpbm_unlock(sc);
	return 0;

fail3:
	bus_dmamap_destroy(sc->sc_dmat, sc->sc_buf_map);
fail2:
	bus_dmamem_unmap(sc->sc_dmat, kva, sc->sc_buf_size);
fail1:
	bus_dmamem_free(sc->sc_dmat, &segs, nsegs);

	return error;
}

/*
 * mbuf subroutines
 */
STATIC void
mvxpbm_free_mbuf(struct mbuf *m, void *buf, size_t size, void *arg)
{
	struct mvxpbm_chunk *chunk = (struct mvxpbm_chunk *)arg;
	int s;

	KASSERT(m != NULL);
	KASSERT(arg != NULL);

	DPRINTFN(3, "free packet %p\n", m);

	chunk->m = NULL;
	s = splvm();
	pool_cache_put(mb_cache, m);
	splx(s);
	return mvxpbm_free_chunk(chunk);
}

/*
 * Exported APIs
 */
/* get mvxpbm device context */
struct mvxpbm_softc *
mvxpbm_device(struct marvell_attach_args *mva)
{
	struct mvxpbm_softc *sc;

	if (sc0 != NULL)
		return sc0;
	if (mva == NULL)
		return NULL;

	/* allocate software emulation context */
	sc = &sc_emul;
	memset(sc, 0, sizeof(*sc));
	sc->sc_emul = 1;
	sc->sc_iot = mva->mva_iot;
	sc->sc_dmat = mva->mva_dmat;

	mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_NET);
	LIST_INIT(&sc->sc_free);
	LIST_INIT(&sc->sc_inuse);

	if (mvxpbm_alloc_buffer(sc) != 0)
		return NULL;
	mvxpbm_evcnt_attach(sc);
	sc0 = sc;
	return sc0;
}

/* allocate new memory chunk */
struct mvxpbm_chunk *
mvxpbm_alloc(struct mvxpbm_softc *sc)
{
	struct mvxpbm_chunk *chunk;

	mvxpbm_lock(sc);

	chunk = LIST_FIRST(&sc->sc_free);
	if (chunk == NULL) {
		mvxpbm_unlock(sc);
		return NULL;
	}

	LIST_REMOVE(chunk, link);
	LIST_INSERT_HEAD(&sc->sc_inuse, chunk, link);

	mvxpbm_unlock(sc);
	return chunk;
}

/* free memory chunk */
void
mvxpbm_free_chunk(struct mvxpbm_chunk *chunk)
{
	struct mvxpbm_softc *sc = chunk->sc;

	KASSERT(chunk->m == NULL);
	DPRINTFN(3, "bm chunk free\n");

	mvxpbm_lock(sc);

	LIST_REMOVE(chunk, link);
	LIST_INSERT_HEAD(&sc->sc_free, chunk, link);

	mvxpbm_unlock(sc);
}

/* prepare mbuf header after Rx */
int
mvxpbm_init_mbuf_hdr(struct mvxpbm_chunk *chunk)
{
	struct mvxpbm_softc *sc = chunk->sc;

	KASSERT(chunk->m == NULL);

	/* add new mbuf header */
	MGETHDR(chunk->m, M_DONTWAIT, MT_DATA);
	if (chunk->m == NULL) {
		aprint_error_dev(sc->sc_dev, "cannot get mbuf\n");
		return ENOBUFS;
	}
	MEXTADD(chunk->m, chunk->buf_va, chunk->buf_size, 0, 
		mvxpbm_free_mbuf, chunk);
	chunk->m->m_flags |= M_EXT_RW;
	chunk->m->m_len = chunk->m->m_pkthdr.len = chunk->buf_size;
	if (sc->sc_chunk_packet_offset)
		m_adj(chunk->m, sc->sc_chunk_packet_offset);

	return 0;
}

/* sync DMA seguments */
void
mvxpbm_dmamap_sync(struct mvxpbm_chunk *chunk, size_t size, int ops)
{
	struct mvxpbm_softc *sc = chunk->sc;

	KASSERT(size <= chunk->buf_size);
	if (size == 0)
		size = chunk->buf_size;

	bus_dmamap_sync(sc->sc_dmat, sc->sc_buf_map, chunk->buf_off, size, ops);
}

/* lock */
void
mvxpbm_lock(struct mvxpbm_softc *sc)
{
	mutex_enter(&sc->sc_mtx);
}

void
mvxpbm_unlock(struct mvxpbm_softc *sc)
{
	mutex_exit(&sc->sc_mtx);
}

/* get params */
const char *
mvxpbm_xname(struct mvxpbm_softc *sc)
{
	if (sc->sc_emul) {
		return "software_bm";
	}
	return device_xname(sc->sc_dev);
}

size_t
mvxpbm_chunk_size(struct mvxpbm_softc *sc)
{
	return sc->sc_chunk_size;
}

uint32_t
mvxpbm_chunk_count(struct mvxpbm_softc *sc)
{
	return sc->sc_chunk_count;
}

off_t
mvxpbm_packet_offset(struct mvxpbm_softc *sc)
{
	return sc->sc_chunk_packet_offset;
}

paddr_t
mvxpbm_buf_pbase(struct mvxpbm_softc *sc)
{
	return sc->sc_buf_pa;
}

size_t
mvxpbm_buf_size(struct mvxpbm_softc *sc)
{
	return sc->sc_buf_size;
}