1244466Scognet/*-
2244466Scognet * Copyright (c) 2012 Ian Lepore
3244466Scognet * All rights reserved.
4244466Scognet *
5244466Scognet * Redistribution and use in source and binary forms, with or without
6244466Scognet * modification, are permitted provided that the following conditions
7244466Scognet * are met:
8244466Scognet * 1. Redistributions of source code must retain the above copyright
9244466Scognet *    notice, this list of conditions and the following disclaimer.
10244466Scognet * 2. Redistributions in binary form must reproduce the above copyright
11244466Scognet *    notice, this list of conditions and the following disclaimer in the
12244466Scognet *    documentation and/or other materials provided with the distribution.
13244466Scognet *
14244466Scognet * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15244466Scognet * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16244466Scognet * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17244466Scognet * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18244466Scognet * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19244466Scognet * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20244466Scognet * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21244466Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22244466Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23244466Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24244466Scognet * SUCH DAMAGE.
25244466Scognet */
26244466Scognet
27244466Scognet#include <sys/cdefs.h>
28244466Scognet__FBSDID("$FreeBSD$");
29244466Scognet
30244466Scognet/*
31244466Scognet * Buffer allocation support routines for bus_dmamem_alloc implementations.
32244466Scognet */
33244466Scognet
34244466Scognet#include <sys/param.h>
35244466Scognet#include <sys/systm.h>
36244466Scognet#include <sys/bus.h>
37244466Scognet#include <sys/busdma_bufalloc.h>
38244466Scognet#include <sys/malloc.h>
39244466Scognet
40244466Scognet#include <vm/vm.h>
41244466Scognet#include <vm/vm_extern.h>
42244466Scognet#include <vm/vm_kern.h>
43244466Scognet#include <vm/uma.h>
44244466Scognet
45244466Scognet/*
46244466Scognet * We manage buffer zones up to a page in size.  Buffers larger than a page can
47244466Scognet * be managed by one of the kernel's page-oriented memory allocation routines as
48244466Scognet * efficiently as what we can do here.  Also, a page is the largest size for
49244466Scognet * which we can g'tee contiguity when using uma, and contiguity is one of the
50244466Scognet * requirements we have to fulfill.
51244466Scognet */
52244466Scognet#define	MIN_ZONE_BUFSIZE	32
53244466Scognet#define	MAX_ZONE_BUFSIZE	PAGE_SIZE
54244466Scognet
55244466Scognet/*
56244466Scognet * The static array of 12 bufzones is big enough to handle all the zones for the
57244466Scognet * smallest supported allocation size of 32 through the largest supported page
58244466Scognet * size of 64K.  If you up the biggest page size number, up the array size too.
59244466Scognet * Basically the size of the array needs to be log2(maxsize)-log2(minsize)+1,
60244466Scognet * but I don't know of an easy way to express that as a compile-time constant.
61244466Scognet */
62244466Scognet#if PAGE_SIZE > 65536
63244466Scognet#error Unsupported page size
64244466Scognet#endif
65244466Scognet
66244466Scognetstruct busdma_bufalloc {
67244466Scognet	bus_size_t		min_size;
68244466Scognet	size_t			num_zones;
69244466Scognet	struct busdma_bufzone	buf_zones[12];
70244466Scognet};
71244466Scognet
72244466Scognetbusdma_bufalloc_t
73244466Scognetbusdma_bufalloc_create(const char *name, bus_size_t minimum_alignment,
74244466Scognet    uma_alloc alloc_func, uma_free free_func, u_int32_t zcreate_flags)
75244466Scognet{
76244466Scognet	struct busdma_bufalloc *ba;
77244466Scognet	struct busdma_bufzone *bz;
78244466Scognet	int i;
79244466Scognet	bus_size_t cursize;
80244466Scognet
81244466Scognet	ba = malloc(sizeof(struct busdma_bufalloc), M_DEVBUF,
82244466Scognet	    M_ZERO | M_WAITOK);
83244466Scognet
84244466Scognet	ba->min_size = MAX(MIN_ZONE_BUFSIZE, minimum_alignment);
85244466Scognet
86244466Scognet	/*
87244466Scognet	 * Each uma zone is created with an alignment of size-1, meaning that
88244466Scognet	 * the alignment is equal to the size (I.E., 64 byte buffers are aligned
89244466Scognet	 * to 64 byte boundaries, etc).  This allows for a fast efficient test
90244466Scognet	 * when deciding whether a pool buffer meets the constraints of a given
91244466Scognet	 * tag used for allocation: the buffer is usable if tag->alignment <=
92244466Scognet	 * bufzone->size.
93244466Scognet	 */
94244466Scognet	for (i = 0, bz = ba->buf_zones, cursize = ba->min_size;
95244466Scognet	    i < nitems(ba->buf_zones) && cursize <= MAX_ZONE_BUFSIZE;
96244466Scognet	    ++i, ++bz, cursize <<= 1) {
97289618Sian		snprintf(bz->name, sizeof(bz->name), "dma %.10s %ju",
98289618Sian		    name, (uintmax_t)cursize);
99244466Scognet		bz->size = cursize;
100244466Scognet		bz->umazone = uma_zcreate(bz->name, bz->size,
101244466Scognet		    NULL, NULL, NULL, NULL, bz->size - 1, zcreate_flags);
102244466Scognet		if (bz->umazone == NULL) {
103244466Scognet			busdma_bufalloc_destroy(ba);
104244466Scognet			return (NULL);
105244466Scognet		}
106244466Scognet		if (alloc_func != NULL)
107244466Scognet			uma_zone_set_allocf(bz->umazone, alloc_func);
108244466Scognet		if (free_func != NULL)
109244466Scognet			uma_zone_set_freef(bz->umazone, free_func);
110244466Scognet		++ba->num_zones;
111244466Scognet	}
112244466Scognet
113244466Scognet	return (ba);
114244466Scognet}
115244466Scognet
116244466Scognetvoid
117244466Scognetbusdma_bufalloc_destroy(busdma_bufalloc_t ba)
118244466Scognet{
119244466Scognet	struct busdma_bufzone *bz;
120244466Scognet	int i;
121244466Scognet
122244466Scognet	if (ba == NULL)
123244466Scognet		return;
124244466Scognet
125244466Scognet	for (i = 0, bz = ba->buf_zones; i < ba->num_zones; ++i, ++bz) {
126244466Scognet		uma_zdestroy(bz->umazone);
127244466Scognet	}
128244466Scognet
129244466Scognet	free(ba, M_DEVBUF);
130244466Scognet}
131244466Scognet
132244466Scognetstruct busdma_bufzone *
133244466Scognetbusdma_bufalloc_findzone(busdma_bufalloc_t ba, bus_size_t size)
134244466Scognet{
135244466Scognet	struct busdma_bufzone *bz;
136244466Scognet	int i;
137244466Scognet
138244466Scognet	if (size > MAX_ZONE_BUFSIZE)
139244466Scognet		return (NULL);
140244466Scognet
141244466Scognet	for (i = 0, bz = ba->buf_zones; i < ba->num_zones; ++i, ++bz) {
142244466Scognet		if (bz->size >= size)
143244466Scognet			return (bz);
144244466Scognet	}
145244466Scognet
146244466Scognet	panic("Didn't find a buffer zone of the right size");
147244466Scognet}
148244466Scognet
149244466Scognetvoid *
150280957Srstonebusdma_bufalloc_alloc_uncacheable(uma_zone_t zone, vm_size_t size,
151280957Srstone    uint8_t *pflag, int wait)
152244466Scognet{
153244466Scognet#ifdef VM_MEMATTR_UNCACHEABLE
154244466Scognet
155254025Sjeff	/* Inform UMA that this allocator uses kernel_arena/object. */
156244466Scognet	*pflag = UMA_SLAB_KERNEL;
157244466Scognet
158254025Sjeff	return ((void *)kmem_alloc_attr(kernel_arena, size, wait, 0,
159244466Scognet	    BUS_SPACE_MAXADDR, VM_MEMATTR_UNCACHEABLE));
160244466Scognet
161244466Scognet#else
162244466Scognet
163244466Scognet	panic("VM_MEMATTR_UNCACHEABLE unavailable");
164244466Scognet
165244466Scognet#endif	/* VM_MEMATTR_UNCACHEABLE */
166244466Scognet}
167244466Scognet
168244466Scognetvoid
169280957Srstonebusdma_bufalloc_free_uncacheable(void *item, vm_size_t size, uint8_t pflag)
170244466Scognet{
171244466Scognet
172254025Sjeff	kmem_free(kernel_arena, (vm_offset_t)item, size);
173244466Scognet}
174244466Scognet
175