xref: /openbmc/linux/drivers/infiniband/hw/mthca/mthca_allocator.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * Copyright (c) 2004 Topspin Communications.  All rights reserved.
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * This software is available to you under a choice of one of two
51da177e4SLinus Torvalds  * licenses.  You may choose to be licensed under the terms of the GNU
61da177e4SLinus Torvalds  * General Public License (GPL) Version 2, available from the file
71da177e4SLinus Torvalds  * COPYING in the main directory of this source tree, or the
81da177e4SLinus Torvalds  * OpenIB.org BSD license below:
91da177e4SLinus Torvalds  *
101da177e4SLinus Torvalds  *     Redistribution and use in source and binary forms, with or
111da177e4SLinus Torvalds  *     without modification, are permitted provided that the following
121da177e4SLinus Torvalds  *     conditions are met:
131da177e4SLinus Torvalds  *
141da177e4SLinus Torvalds  *      - Redistributions of source code must retain the above
151da177e4SLinus Torvalds  *        copyright notice, this list of conditions and the following
161da177e4SLinus Torvalds  *        disclaimer.
171da177e4SLinus Torvalds  *
181da177e4SLinus Torvalds  *      - Redistributions in binary form must reproduce the above
191da177e4SLinus Torvalds  *        copyright notice, this list of conditions and the following
201da177e4SLinus Torvalds  *        disclaimer in the documentation and/or other materials
211da177e4SLinus Torvalds  *        provided with the distribution.
221da177e4SLinus Torvalds  *
231da177e4SLinus Torvalds  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
241da177e4SLinus Torvalds  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
251da177e4SLinus Torvalds  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
261da177e4SLinus Torvalds  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
271da177e4SLinus Torvalds  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
281da177e4SLinus Torvalds  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
291da177e4SLinus Torvalds  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
301da177e4SLinus Torvalds  * SOFTWARE.
311da177e4SLinus Torvalds  */
321da177e4SLinus Torvalds 
331da177e4SLinus Torvalds #include <linux/errno.h>
341da177e4SLinus Torvalds #include <linux/slab.h>
351da177e4SLinus Torvalds #include <linux/bitmap.h>
361da177e4SLinus Torvalds 
371da177e4SLinus Torvalds #include "mthca_dev.h"
381da177e4SLinus Torvalds 
391da177e4SLinus Torvalds /* Trivial bitmap-based allocator */
mthca_alloc(struct mthca_alloc * alloc)401da177e4SLinus Torvalds u32 mthca_alloc(struct mthca_alloc *alloc)
411da177e4SLinus Torvalds {
425a4e6dccSRoland Dreier 	unsigned long flags;
431da177e4SLinus Torvalds 	u32 obj;
441da177e4SLinus Torvalds 
455a4e6dccSRoland Dreier 	spin_lock_irqsave(&alloc->lock, flags);
465a4e6dccSRoland Dreier 
471da177e4SLinus Torvalds 	obj = find_next_zero_bit(alloc->table, alloc->max, alloc->last);
481da177e4SLinus Torvalds 	if (obj >= alloc->max) {
491da177e4SLinus Torvalds 		alloc->top = (alloc->top + alloc->max) & alloc->mask;
501da177e4SLinus Torvalds 		obj = find_first_zero_bit(alloc->table, alloc->max);
511da177e4SLinus Torvalds 	}
521da177e4SLinus Torvalds 
531da177e4SLinus Torvalds 	if (obj < alloc->max) {
54*19453f34SChristophe JAILLET 		__set_bit(obj, alloc->table);
551da177e4SLinus Torvalds 		obj |= alloc->top;
561da177e4SLinus Torvalds 	} else
571da177e4SLinus Torvalds 		obj = -1;
581da177e4SLinus Torvalds 
595a4e6dccSRoland Dreier 	spin_unlock_irqrestore(&alloc->lock, flags);
601da177e4SLinus Torvalds 
611da177e4SLinus Torvalds 	return obj;
621da177e4SLinus Torvalds }
631da177e4SLinus Torvalds 
mthca_free(struct mthca_alloc * alloc,u32 obj)641da177e4SLinus Torvalds void mthca_free(struct mthca_alloc *alloc, u32 obj)
651da177e4SLinus Torvalds {
665a4e6dccSRoland Dreier 	unsigned long flags;
675a4e6dccSRoland Dreier 
681da177e4SLinus Torvalds 	obj &= alloc->max - 1;
695a4e6dccSRoland Dreier 
705a4e6dccSRoland Dreier 	spin_lock_irqsave(&alloc->lock, flags);
715a4e6dccSRoland Dreier 
72*19453f34SChristophe JAILLET 	__clear_bit(obj, alloc->table);
731da177e4SLinus Torvalds 	alloc->last = min(alloc->last, obj);
741da177e4SLinus Torvalds 	alloc->top = (alloc->top + alloc->max) & alloc->mask;
755a4e6dccSRoland Dreier 
765a4e6dccSRoland Dreier 	spin_unlock_irqrestore(&alloc->lock, flags);
771da177e4SLinus Torvalds }
781da177e4SLinus Torvalds 
mthca_alloc_init(struct mthca_alloc * alloc,u32 num,u32 mask,u32 reserved)791da177e4SLinus Torvalds int mthca_alloc_init(struct mthca_alloc *alloc, u32 num, u32 mask,
801da177e4SLinus Torvalds 		     u32 reserved)
811da177e4SLinus Torvalds {
821da177e4SLinus Torvalds 	/* num must be a power of 2 */
831da177e4SLinus Torvalds 	if (num != 1 << (ffs(num) - 1))
841da177e4SLinus Torvalds 		return -EINVAL;
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds 	alloc->last = 0;
871da177e4SLinus Torvalds 	alloc->top  = 0;
881da177e4SLinus Torvalds 	alloc->max  = num;
891da177e4SLinus Torvalds 	alloc->mask = mask;
901da177e4SLinus Torvalds 	spin_lock_init(&alloc->lock);
9112d1e2f3SChristophe JAILLET 	alloc->table = bitmap_zalloc(num, GFP_KERNEL);
921da177e4SLinus Torvalds 	if (!alloc->table)
931da177e4SLinus Torvalds 		return -ENOMEM;
941da177e4SLinus Torvalds 
95a277f383SChristophe JAILLET 	bitmap_set(alloc->table, 0, reserved);
961da177e4SLinus Torvalds 
971da177e4SLinus Torvalds 	return 0;
981da177e4SLinus Torvalds }
991da177e4SLinus Torvalds 
mthca_alloc_cleanup(struct mthca_alloc * alloc)1001da177e4SLinus Torvalds void mthca_alloc_cleanup(struct mthca_alloc *alloc)
1011da177e4SLinus Torvalds {
10212d1e2f3SChristophe JAILLET 	bitmap_free(alloc->table);
1031da177e4SLinus Torvalds }
1041da177e4SLinus Torvalds 
1051da177e4SLinus Torvalds /*
1061da177e4SLinus Torvalds  * Array of pointers with lazy allocation of leaf pages.  Callers of
1071da177e4SLinus Torvalds  * _get, _set and _clear methods must use a lock or otherwise
1081da177e4SLinus Torvalds  * serialize access to the array.
1091da177e4SLinus Torvalds  */
1101da177e4SLinus Torvalds 
11169e9fbb4SRoland Dreier #define MTHCA_ARRAY_MASK (PAGE_SIZE / sizeof (void *) - 1)
11269e9fbb4SRoland Dreier 
mthca_array_get(struct mthca_array * array,int index)1131da177e4SLinus Torvalds void *mthca_array_get(struct mthca_array *array, int index)
1141da177e4SLinus Torvalds {
1151da177e4SLinus Torvalds 	int p = (index * sizeof (void *)) >> PAGE_SHIFT;
1161da177e4SLinus Torvalds 
11769e9fbb4SRoland Dreier 	if (array->page_list[p].page)
11869e9fbb4SRoland Dreier 		return array->page_list[p].page[index & MTHCA_ARRAY_MASK];
11969e9fbb4SRoland Dreier 	else
1201da177e4SLinus Torvalds 		return NULL;
1211da177e4SLinus Torvalds }
1221da177e4SLinus Torvalds 
mthca_array_set(struct mthca_array * array,int index,void * value)1231da177e4SLinus Torvalds int mthca_array_set(struct mthca_array *array, int index, void *value)
1241da177e4SLinus Torvalds {
1251da177e4SLinus Torvalds 	int p = (index * sizeof (void *)) >> PAGE_SHIFT;
1261da177e4SLinus Torvalds 
1271da177e4SLinus Torvalds 	/* Allocate with GFP_ATOMIC because we'll be called with locks held. */
1281da177e4SLinus Torvalds 	if (!array->page_list[p].page)
1291da177e4SLinus Torvalds 		array->page_list[p].page = (void **) get_zeroed_page(GFP_ATOMIC);
1301da177e4SLinus Torvalds 
1311da177e4SLinus Torvalds 	if (!array->page_list[p].page)
1321da177e4SLinus Torvalds 		return -ENOMEM;
1331da177e4SLinus Torvalds 
13469e9fbb4SRoland Dreier 	array->page_list[p].page[index & MTHCA_ARRAY_MASK] = value;
1351da177e4SLinus Torvalds 	++array->page_list[p].used;
1361da177e4SLinus Torvalds 
1371da177e4SLinus Torvalds 	return 0;
1381da177e4SLinus Torvalds }
1391da177e4SLinus Torvalds 
mthca_array_clear(struct mthca_array * array,int index)1401da177e4SLinus Torvalds void mthca_array_clear(struct mthca_array *array, int index)
1411da177e4SLinus Torvalds {
1421da177e4SLinus Torvalds 	int p = (index * sizeof (void *)) >> PAGE_SHIFT;
1431da177e4SLinus Torvalds 
1441da177e4SLinus Torvalds 	if (--array->page_list[p].used == 0) {
1451da177e4SLinus Torvalds 		free_page((unsigned long) array->page_list[p].page);
1461da177e4SLinus Torvalds 		array->page_list[p].page = NULL;
147bf74c747SMichael S. Tsirkin 	} else
14869e9fbb4SRoland Dreier 		array->page_list[p].page[index & MTHCA_ARRAY_MASK] = NULL;
1491da177e4SLinus Torvalds 
1501da177e4SLinus Torvalds 	if (array->page_list[p].used < 0)
1511da177e4SLinus Torvalds 		pr_debug("Array %p index %d page %d with ref count %d < 0\n",
1521da177e4SLinus Torvalds 			 array, index, p, array->page_list[p].used);
1531da177e4SLinus Torvalds }
1541da177e4SLinus Torvalds 
mthca_array_init(struct mthca_array * array,int nent)1551da177e4SLinus Torvalds int mthca_array_init(struct mthca_array *array, int nent)
1561da177e4SLinus Torvalds {
1571da177e4SLinus Torvalds 	int npage = (nent * sizeof (void *) + PAGE_SIZE - 1) / PAGE_SIZE;
1581da177e4SLinus Torvalds 	int i;
1591da177e4SLinus Torvalds 
1606da2ec56SKees Cook 	array->page_list = kmalloc_array(npage, sizeof(*array->page_list),
1616da2ec56SKees Cook 					 GFP_KERNEL);
1621da177e4SLinus Torvalds 	if (!array->page_list)
1631da177e4SLinus Torvalds 		return -ENOMEM;
1641da177e4SLinus Torvalds 
1651da177e4SLinus Torvalds 	for (i = 0; i < npage; ++i) {
1661da177e4SLinus Torvalds 		array->page_list[i].page = NULL;
1671da177e4SLinus Torvalds 		array->page_list[i].used = 0;
1681da177e4SLinus Torvalds 	}
1691da177e4SLinus Torvalds 
1701da177e4SLinus Torvalds 	return 0;
1711da177e4SLinus Torvalds }
1721da177e4SLinus Torvalds 
mthca_array_cleanup(struct mthca_array * array,int nent)1731da177e4SLinus Torvalds void mthca_array_cleanup(struct mthca_array *array, int nent)
1741da177e4SLinus Torvalds {
1751da177e4SLinus Torvalds 	int i;
1761da177e4SLinus Torvalds 
1771da177e4SLinus Torvalds 	for (i = 0; i < (nent * sizeof (void *) + PAGE_SIZE - 1) / PAGE_SIZE; ++i)
1781da177e4SLinus Torvalds 		free_page((unsigned long) array->page_list[i].page);
1791da177e4SLinus Torvalds 
1801da177e4SLinus Torvalds 	kfree(array->page_list);
1811da177e4SLinus Torvalds }
18287b81670SRoland Dreier 
18387b81670SRoland Dreier /*
18487b81670SRoland Dreier  * Handling for queue buffers -- we allocate a bunch of memory and
18587b81670SRoland Dreier  * register it in a memory region at HCA virtual address 0.  If the
18687b81670SRoland Dreier  * requested size is > max_direct, we split the allocation into
18787b81670SRoland Dreier  * multiple pages, so we don't require too much contiguous memory.
18887b81670SRoland Dreier  */
18987b81670SRoland Dreier 
mthca_buf_alloc(struct mthca_dev * dev,int size,int max_direct,union mthca_buf * buf,int * is_direct,struct mthca_pd * pd,int hca_write,struct mthca_mr * mr)19087b81670SRoland Dreier int mthca_buf_alloc(struct mthca_dev *dev, int size, int max_direct,
19187b81670SRoland Dreier 		    union mthca_buf *buf, int *is_direct, struct mthca_pd *pd,
19287b81670SRoland Dreier 		    int hca_write, struct mthca_mr *mr)
19387b81670SRoland Dreier {
19487b81670SRoland Dreier 	int err = -ENOMEM;
19587b81670SRoland Dreier 	int npages, shift;
19687b81670SRoland Dreier 	u64 *dma_list = NULL;
19787b81670SRoland Dreier 	dma_addr_t t;
19887b81670SRoland Dreier 	int i;
19987b81670SRoland Dreier 
20087b81670SRoland Dreier 	if (size <= max_direct) {
20187b81670SRoland Dreier 		*is_direct = 1;
20287b81670SRoland Dreier 		npages     = 1;
20387b81670SRoland Dreier 		shift      = get_order(size) + PAGE_SHIFT;
20487b81670SRoland Dreier 
20587b81670SRoland Dreier 		buf->direct.buf = dma_alloc_coherent(&dev->pdev->dev,
20687b81670SRoland Dreier 						     size, &t, GFP_KERNEL);
20787b81670SRoland Dreier 		if (!buf->direct.buf)
20887b81670SRoland Dreier 			return -ENOMEM;
20987b81670SRoland Dreier 
2103a2baff7SFUJITA Tomonori 		dma_unmap_addr_set(&buf->direct, mapping, t);
21187b81670SRoland Dreier 
21287b81670SRoland Dreier 		while (t & ((1 << shift) - 1)) {
21387b81670SRoland Dreier 			--shift;
21487b81670SRoland Dreier 			npages *= 2;
21587b81670SRoland Dreier 		}
21687b81670SRoland Dreier 
2176da2ec56SKees Cook 		dma_list = kmalloc_array(npages, sizeof(*dma_list),
2186da2ec56SKees Cook 					 GFP_KERNEL);
21987b81670SRoland Dreier 		if (!dma_list)
22087b81670SRoland Dreier 			goto err_free;
22187b81670SRoland Dreier 
22287b81670SRoland Dreier 		for (i = 0; i < npages; ++i)
22387b81670SRoland Dreier 			dma_list[i] = t + i * (1 << shift);
22487b81670SRoland Dreier 	} else {
22587b81670SRoland Dreier 		*is_direct = 0;
22687b81670SRoland Dreier 		npages     = (size + PAGE_SIZE - 1) / PAGE_SIZE;
22787b81670SRoland Dreier 		shift      = PAGE_SHIFT;
22887b81670SRoland Dreier 
2296da2ec56SKees Cook 		dma_list = kmalloc_array(npages, sizeof(*dma_list),
2306da2ec56SKees Cook 					 GFP_KERNEL);
23187b81670SRoland Dreier 		if (!dma_list)
23287b81670SRoland Dreier 			return -ENOMEM;
23387b81670SRoland Dreier 
2346da2ec56SKees Cook 		buf->page_list = kmalloc_array(npages,
2356da2ec56SKees Cook 					       sizeof(*buf->page_list),
23687b81670SRoland Dreier 					       GFP_KERNEL);
23787b81670SRoland Dreier 		if (!buf->page_list)
23887b81670SRoland Dreier 			goto err_out;
23987b81670SRoland Dreier 
24087b81670SRoland Dreier 		for (i = 0; i < npages; ++i)
24187b81670SRoland Dreier 			buf->page_list[i].buf = NULL;
24287b81670SRoland Dreier 
24387b81670SRoland Dreier 		for (i = 0; i < npages; ++i) {
24487b81670SRoland Dreier 			buf->page_list[i].buf =
24587b81670SRoland Dreier 				dma_alloc_coherent(&dev->pdev->dev, PAGE_SIZE,
24687b81670SRoland Dreier 						   &t, GFP_KERNEL);
24787b81670SRoland Dreier 			if (!buf->page_list[i].buf)
24887b81670SRoland Dreier 				goto err_free;
24987b81670SRoland Dreier 
25087b81670SRoland Dreier 			dma_list[i] = t;
2513a2baff7SFUJITA Tomonori 			dma_unmap_addr_set(&buf->page_list[i], mapping, t);
25287b81670SRoland Dreier 
2538909c571SShani Moideen 			clear_page(buf->page_list[i].buf);
25487b81670SRoland Dreier 		}
25587b81670SRoland Dreier 	}
25687b81670SRoland Dreier 
25787b81670SRoland Dreier 	err = mthca_mr_alloc_phys(dev, pd->pd_num,
25887b81670SRoland Dreier 				  dma_list, shift, npages,
25987b81670SRoland Dreier 				  0, size,
26087b81670SRoland Dreier 				  MTHCA_MPT_FLAG_LOCAL_READ |
26187b81670SRoland Dreier 				  (hca_write ? MTHCA_MPT_FLAG_LOCAL_WRITE : 0),
26287b81670SRoland Dreier 				  mr);
26387b81670SRoland Dreier 	if (err)
26487b81670SRoland Dreier 		goto err_free;
26587b81670SRoland Dreier 
26687b81670SRoland Dreier 	kfree(dma_list);
26787b81670SRoland Dreier 
26887b81670SRoland Dreier 	return 0;
26987b81670SRoland Dreier 
27087b81670SRoland Dreier err_free:
27187b81670SRoland Dreier 	mthca_buf_free(dev, size, buf, *is_direct, NULL);
27287b81670SRoland Dreier 
27387b81670SRoland Dreier err_out:
27487b81670SRoland Dreier 	kfree(dma_list);
27587b81670SRoland Dreier 
27687b81670SRoland Dreier 	return err;
27787b81670SRoland Dreier }
27887b81670SRoland Dreier 
mthca_buf_free(struct mthca_dev * dev,int size,union mthca_buf * buf,int is_direct,struct mthca_mr * mr)27987b81670SRoland Dreier void mthca_buf_free(struct mthca_dev *dev, int size, union mthca_buf *buf,
28087b81670SRoland Dreier 		    int is_direct, struct mthca_mr *mr)
28187b81670SRoland Dreier {
28287b81670SRoland Dreier 	int i;
28387b81670SRoland Dreier 
28487b81670SRoland Dreier 	if (mr)
28587b81670SRoland Dreier 		mthca_free_mr(dev, mr);
28687b81670SRoland Dreier 
28787b81670SRoland Dreier 	if (is_direct)
28887b81670SRoland Dreier 		dma_free_coherent(&dev->pdev->dev, size, buf->direct.buf,
2893a2baff7SFUJITA Tomonori 				  dma_unmap_addr(&buf->direct, mapping));
29087b81670SRoland Dreier 	else {
29187b81670SRoland Dreier 		for (i = 0; i < (size + PAGE_SIZE - 1) / PAGE_SIZE; ++i)
29287b81670SRoland Dreier 			dma_free_coherent(&dev->pdev->dev, PAGE_SIZE,
29387b81670SRoland Dreier 					  buf->page_list[i].buf,
2943a2baff7SFUJITA Tomonori 					  dma_unmap_addr(&buf->page_list[i],
29587b81670SRoland Dreier 							 mapping));
29687b81670SRoland Dreier 		kfree(buf->page_list);
29787b81670SRoland Dreier 	}
29887b81670SRoland Dreier }
299