text: /*-
   * Copyright (c) 2003
   *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
   * 	All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   *
   * Author: Hartmut Brandt <harti@freebsd.org>
   */
  
  #include <sys/cdefs.h>
  __FBSDID("$FreeBSD: releng/11.0/sys/kern/subr_mbpool.c 298819 2016-04-29 22:15:33Z pfg $");
  
  #include <sys/param.h>
  #include <sys/lock.h>
  #include <sys/mutex.h>
  #include <sys/kernel.h>
  #include <sys/systm.h>
  #include <sys/malloc.h>
  #include <sys/module.h>
  
  #include <machine/bus.h>
  
  #include <sys/mbuf.h>
  #include <sys/mbpool.h>
  
  MODULE_VERSION(libmbpool, 1);
  
  /*
   * Memory is allocated as DMA-able pages. Each page is divided into a number
   * of equal chunks where the last 4 bytes of each chunk are occupied by
   * the page number and the chunk number. The caller must take these four
   * bytes into account when specifying the chunk size. Each page is mapped by
   * its own DMA map using the user specified DMA tag.
   *
   * Each chunk has a used and a card bit in the high bits of its page number.
   *  0    0	chunk is free and may be allocated
   *  1    1	chunk has been given to the interface
   *  0    1	chunk is traveling through the system
   *  1    0	illegal
   */
  struct mbtrail {
  	uint16_t	chunk;
  	uint16_t	page;
  };
  #define	MBP_CARD	0x8000
  #define	MBP_USED	0x4000
  #define	MBP_PMSK	0x3fff		/* page number mask */
  #define	MBP_CMSK	0x01ff		/* chunk number mask */
  
  struct mbfree {
  	SLIST_ENTRY(mbfree) link;	/* link on free list */
  };
  
  struct mbpage {
  	bus_dmamap_t	map;		/* map for this page */
  	bus_addr_t	phy;		/* physical address */
  	void		*va;		/* the memory */
  };
  
  struct mbpool {
  	const char	*name;		/* a name for this pool */
  	bus_dma_tag_t	dmat;		/* tag for mapping */
  	u_int		max_pages;	/* maximum number of pages */
  	size_t		page_size;	/* size of each allocation */
  	size_t		chunk_size;	/* size of each external mbuf */
  
  	struct mtx	free_lock;	/* lock of free list */
  	SLIST_HEAD(, mbfree) free_list;	/* free list */
  	u_int		npages;		/* current number of pages */
  	u_int		nchunks;	/* chunks per page */
  	struct mbpage	pages[];	/* pages */
  };
  
  static MALLOC_DEFINE(M_MBPOOL, "mbpools", "mbuf pools");
  
  /*
   * Make a trail pointer from a chunk pointer
   */
  #define	C2T(P, C)	((struct mbtrail *)((char *)(C) + (P)->chunk_size - \
  			    sizeof(struct mbtrail)))
  
  /*
   * Make a free chunk pointer from a chunk number
   */
  #define	N2C(P, PG, C)	((struct mbfree *)((char *)(PG)->va + \
  			    (C) * (P)->chunk_size))
  
  /*
   * Make/parse handles
   */
  #define	HMAKE(P, C)	((((P) & MBP_PMSK) << 16) | ((C) << 7))
  #define	HPAGE(H)	(((H) >> 16) & MBP_PMSK)
  #define	HCHUNK(H)	(((H) >>  7) & MBP_CMSK)
  
  /*
   * initialize a pool
   */
  int
  mbp_create(struct mbpool **pp, const char *name, bus_dma_tag_t dmat,
      u_int max_pages, size_t page_size, size_t chunk_size)
  {
  	u_int nchunks;
  
  	if (max_pages > MBPOOL_MAX_MAXPAGES || chunk_size == 0)
  		return (EINVAL);
  	nchunks = page_size / chunk_size;
  	if (nchunks == 0 || nchunks > MBPOOL_MAX_CHUNKS)
  		return (EINVAL);
  
  	(*pp) = malloc(sizeof(struct mbpool) +
  	    max_pages * sizeof(struct mbpage),
  	    M_MBPOOL, M_WAITOK | M_ZERO);
  
  	(*pp)->name = name;
  	(*pp)->dmat = dmat;
  	(*pp)->max_pages = max_pages;
  	(*pp)->page_size = page_size;
  	(*pp)->chunk_size = chunk_size;
  	(*pp)->nchunks = nchunks;
  
  	SLIST_INIT(&(*pp)->free_list);
  	mtx_init(&(*pp)->free_lock, name, NULL, MTX_DEF);
  
  	return (0);
  }
  
  /*
   * destroy a pool
   */
  void
  mbp_destroy(struct mbpool *p)
  {
  	u_int i;
  	struct mbpage *pg;
  #ifdef DIAGNOSTIC
  	struct mbtrail *tr;
  	u_int b;
  #endif
  
  	for (i = 0; i < p->npages; i++) {
  		pg = &p->pages[i];
  #ifdef DIAGNOSTIC
  		for (b = 0; b < p->nchunks; b++) {
  			tr = C2T(p, N2C(p, pg, b));
  			if (tr->page & MBP_CARD)
  				printf("%s: (%s) buf still on card"
  				    " %u/%u\n", __func__, p->name, i, b);
  			if (tr->page & MBP_USED)
  				printf("%s: (%s) sbuf still in use"
  				    " %u/%u\n", __func__, p->name, i, b);
  		}
  #endif
  		bus_dmamap_unload(p->dmat, pg->map);
  		bus_dmamem_free(p->dmat, pg->va, pg->map);
  	}
  	mtx_destroy(&p->free_lock);
  
  	free(p, M_MBPOOL);
  }
  
  /*
   * Helper function when loading a one segment DMA buffer.
   */
  static void
  mbp_callback(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
  {
  	if (error == 0)
  		*(bus_addr_t *)arg = segs[0].ds_addr;
  }
  
  /*
   * Allocate a new page
   */
  static void
  mbp_alloc_page(struct mbpool *p)
  {
  	int error;
  	struct mbpage *pg;
  	u_int i;
  	struct mbfree *f;
  	struct mbtrail *t;
  
  	if (p->npages == p->max_pages) {
  #ifdef DIAGNOSTIC
  		printf("%s: (%s) page limit reached %u\n", __func__,
  		    p->name, p->max_pages);
  #endif
  		return;
  	}
  	pg = &p->pages[p->npages];
  
  	error = bus_dmamem_alloc(p->dmat, &pg->va, BUS_DMA_NOWAIT, &pg->map);
  	if (error != 0)
  		return;
  
  	error = bus_dmamap_load(p->dmat, pg->map, pg->va, p->page_size,
  	    mbp_callback, &pg->phy, 0);
  	if (error != 0) {
  		bus_dmamem_free(p->dmat, pg->va, pg->map);
  		return;
  	}
  
  	for (i = 0; i < p->nchunks; i++) {
  		f = N2C(p, pg, i);
  		t = C2T(p, f);
  		t->page = p->npages;
  		t->chunk = i;
  		SLIST_INSERT_HEAD(&p->free_list, f, link);
  	}
  
  	p->npages++;
  }
  
  /*
   * allocate a chunk
   */
  void *
  mbp_alloc(struct mbpool *p, bus_addr_t *pap, uint32_t *hp)
  {
  	struct mbfree *cf;
  	struct mbtrail *t;
  
  	mtx_lock(&p->free_lock);
  	if ((cf = SLIST_FIRST(&p->free_list)) == NULL) {
  		mbp_alloc_page(p);
  		cf = SLIST_FIRST(&p->free_list);
  	}
  	if (cf == NULL) {
  		mtx_unlock(&p->free_lock);
  		return (NULL);
  	}
  	SLIST_REMOVE_HEAD(&p->free_list, link);
  	mtx_unlock(&p->free_lock);
  
  	t = C2T(p, cf);
  
  	*pap = p->pages[t->page].phy + t->chunk * p->chunk_size;
  	*hp = HMAKE(t->page, t->chunk);
  
  	t->page |= MBP_CARD | MBP_USED;
  
  	return (cf);
  }
  
  /*
   * Free a chunk
   */
  void
  mbp_free(struct mbpool *p, void *ptr)
  {
  	struct mbtrail *t;
  
  	mtx_lock(&p->free_lock);
  	t = C2T(p, ptr);
  	t->page &= ~(MBP_USED | MBP_CARD);
  	SLIST_INSERT_HEAD(&p->free_list, (struct mbfree *)ptr, link);
  	mtx_unlock(&p->free_lock);
  }
  
  /*
   * Mbuf system external mbuf free routine
   */
  void
  mbp_ext_free(struct mbuf *m, void *buf, void *arg)
  {
  
  	mbp_free(arg, buf);
  }
  
  /*
   * Free all buffers that are marked as being on the card
   */
  void
  mbp_card_free(struct mbpool *p)
  {
  	u_int i, b;
  	struct mbpage *pg;
  	struct mbtrail *tr;
  	struct mbfree *cf;
  
  	mtx_lock(&p->free_lock);
  	for (i = 0; i < p->npages; i++) {
  		pg = &p->pages[i];
  		for (b = 0; b < p->nchunks; b++) {
  			cf = N2C(p, pg, b);
  			tr = C2T(p, cf);
  			if (tr->page & MBP_CARD) {
  				tr->page &= MBP_PMSK;
  				SLIST_INSERT_HEAD(&p->free_list, cf, link);
  			}
  		}
  	}
  	mtx_unlock(&p->free_lock);
  }
  
  /*
   * Count buffers
   */
  void
  mbp_count(struct mbpool *p, u_int *used, u_int *card, u_int *free)
  {
  	u_int i, b;
  	struct mbpage *pg;
  	struct mbtrail *tr;
  	struct mbfree *cf;
  
  	*used = *card = *free = 0;
  	for (i = 0; i < p->npages; i++) {
  		pg = &p->pages[i];
  		for (b = 0; b < p->nchunks; b++) {
  			tr = C2T(p, N2C(p, pg, b));
  			if (tr->page & MBP_CARD)
  				(*card)++;
  			if (tr->page & MBP_USED)
  				(*used)++;
  		}
  	}
  	mtx_lock(&p->free_lock);
  	SLIST_FOREACH(cf, &p->free_list, link)
  		(*free)++;
  	mtx_unlock(&p->free_lock);
  }
  
  /*
   * Get the buffer from a handle and clear the card flag.
   */
  void *
  mbp_get(struct mbpool *p, uint32_t h)
  {
  	struct mbfree *cf;
  	struct mbtrail *tr;
  
  	cf = N2C(p, &p->pages[HPAGE(h)], HCHUNK(h));
  	tr = C2T(p, cf);
  
  #ifdef DIAGNOSTIC
  	if (!(tr->page & MBP_CARD))
  		printf("%s: (%s) chunk %u page %u not on card\n", __func__,
  		    p->name, HCHUNK(h), HPAGE(h));
  #endif
  
  	tr->page &= ~MBP_CARD;
  	return (cf);
  }
  
  /*
   * Get the buffer from a handle and keep the card flag.
   */
  void *
  mbp_get_keep(struct mbpool *p, uint32_t h)
  {
  	struct mbfree *cf;
  	struct mbtrail *tr;
  
  	cf = N2C(p, &p->pages[HPAGE(h)], HCHUNK(h));
  	tr = C2T(p, cf);
  
  #ifdef DIAGNOSTIC
  	if (!(tr->page & MBP_CARD))
  		printf("%s: (%s) chunk %u page %u not on card\n", __func__,
  		    p->name, HCHUNK(h), HPAGE(h));
  #endif
  
  	return (cf);
  }
  
  /*
   * sync the chunk
   */
  void
  mbp_sync(struct mbpool *p, uint32_t h, bus_addr_t off, bus_size_t len, u_int op)
  {
  
  #if 0
  	bus_dmamap_sync_size(p->dmat, p->pages[HPAGE(h)].map,
  	    HCHUNK(h) * p->chunk_size + off, len, op);
  #endif
  }
  