162306a36Sopenharmony_ci/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2017 Hisilicon Limited.
462306a36Sopenharmony_ci * Copyright (c) 2007, 2008 Mellanox Technologies. All rights reserved.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <rdma/ib_umem.h>
862306a36Sopenharmony_ci#include "hns_roce_device.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ciint hns_roce_db_map_user(struct hns_roce_ucontext *context, unsigned long virt,
1162306a36Sopenharmony_ci			 struct hns_roce_db *db)
1262306a36Sopenharmony_ci{
1362306a36Sopenharmony_ci	unsigned long page_addr = virt & PAGE_MASK;
1462306a36Sopenharmony_ci	struct hns_roce_user_db_page *page;
1562306a36Sopenharmony_ci	unsigned int offset;
1662306a36Sopenharmony_ci	int ret = 0;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	mutex_lock(&context->page_mutex);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci	list_for_each_entry(page, &context->page_list, list)
2162306a36Sopenharmony_ci		if (page->user_virt == page_addr)
2262306a36Sopenharmony_ci			goto found;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	page = kmalloc(sizeof(*page), GFP_KERNEL);
2562306a36Sopenharmony_ci	if (!page) {
2662306a36Sopenharmony_ci		ret = -ENOMEM;
2762306a36Sopenharmony_ci		goto out;
2862306a36Sopenharmony_ci	}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	refcount_set(&page->refcount, 1);
3162306a36Sopenharmony_ci	page->user_virt = page_addr;
3262306a36Sopenharmony_ci	page->umem = ib_umem_get(context->ibucontext.device, page_addr,
3362306a36Sopenharmony_ci				 PAGE_SIZE, 0);
3462306a36Sopenharmony_ci	if (IS_ERR(page->umem)) {
3562306a36Sopenharmony_ci		ret = PTR_ERR(page->umem);
3662306a36Sopenharmony_ci		kfree(page);
3762306a36Sopenharmony_ci		goto out;
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	list_add(&page->list, &context->page_list);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cifound:
4362306a36Sopenharmony_ci	offset = virt - page_addr;
4462306a36Sopenharmony_ci	db->dma = sg_dma_address(page->umem->sgt_append.sgt.sgl) + offset;
4562306a36Sopenharmony_ci	db->virt_addr = sg_virt(page->umem->sgt_append.sgt.sgl) + offset;
4662306a36Sopenharmony_ci	db->u.user_page = page;
4762306a36Sopenharmony_ci	refcount_inc(&page->refcount);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ciout:
5062306a36Sopenharmony_ci	mutex_unlock(&context->page_mutex);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return ret;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_civoid hns_roce_db_unmap_user(struct hns_roce_ucontext *context,
5662306a36Sopenharmony_ci			    struct hns_roce_db *db)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	mutex_lock(&context->page_mutex);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	refcount_dec(&db->u.user_page->refcount);
6162306a36Sopenharmony_ci	if (refcount_dec_if_one(&db->u.user_page->refcount)) {
6262306a36Sopenharmony_ci		list_del(&db->u.user_page->list);
6362306a36Sopenharmony_ci		ib_umem_release(db->u.user_page->umem);
6462306a36Sopenharmony_ci		kfree(db->u.user_page);
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	mutex_unlock(&context->page_mutex);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic struct hns_roce_db_pgdir *hns_roce_alloc_db_pgdir(
7162306a36Sopenharmony_ci					struct device *dma_device)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct hns_roce_db_pgdir *pgdir;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	pgdir = kzalloc(sizeof(*pgdir), GFP_KERNEL);
7662306a36Sopenharmony_ci	if (!pgdir)
7762306a36Sopenharmony_ci		return NULL;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	bitmap_fill(pgdir->order1,
8062306a36Sopenharmony_ci		    HNS_ROCE_DB_PER_PAGE / HNS_ROCE_DB_TYPE_COUNT);
8162306a36Sopenharmony_ci	pgdir->bits[0] = pgdir->order0;
8262306a36Sopenharmony_ci	pgdir->bits[1] = pgdir->order1;
8362306a36Sopenharmony_ci	pgdir->page = dma_alloc_coherent(dma_device, PAGE_SIZE,
8462306a36Sopenharmony_ci					 &pgdir->db_dma, GFP_KERNEL);
8562306a36Sopenharmony_ci	if (!pgdir->page) {
8662306a36Sopenharmony_ci		kfree(pgdir);
8762306a36Sopenharmony_ci		return NULL;
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	return pgdir;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int hns_roce_alloc_db_from_pgdir(struct hns_roce_db_pgdir *pgdir,
9462306a36Sopenharmony_ci					struct hns_roce_db *db, int order)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	unsigned long o;
9762306a36Sopenharmony_ci	unsigned long i;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	for (o = order; o <= 1; ++o) {
10062306a36Sopenharmony_ci		i = find_first_bit(pgdir->bits[o], HNS_ROCE_DB_PER_PAGE >> o);
10162306a36Sopenharmony_ci		if (i < HNS_ROCE_DB_PER_PAGE >> o)
10262306a36Sopenharmony_ci			goto found;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return -ENOMEM;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cifound:
10862306a36Sopenharmony_ci	clear_bit(i, pgdir->bits[o]);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	i <<= o;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (o > order)
11362306a36Sopenharmony_ci		set_bit(i ^ 1, pgdir->bits[order]);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	db->u.pgdir	= pgdir;
11662306a36Sopenharmony_ci	db->index	= i;
11762306a36Sopenharmony_ci	db->db_record	= pgdir->page + db->index;
11862306a36Sopenharmony_ci	db->dma		= pgdir->db_dma  + db->index * HNS_ROCE_DB_UNIT_SIZE;
11962306a36Sopenharmony_ci	db->order	= order;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ciint hns_roce_alloc_db(struct hns_roce_dev *hr_dev, struct hns_roce_db *db,
12562306a36Sopenharmony_ci		      int order)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct hns_roce_db_pgdir *pgdir;
12862306a36Sopenharmony_ci	int ret = 0;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	mutex_lock(&hr_dev->pgdir_mutex);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	list_for_each_entry(pgdir, &hr_dev->pgdir_list, list)
13362306a36Sopenharmony_ci		if (!hns_roce_alloc_db_from_pgdir(pgdir, db, order))
13462306a36Sopenharmony_ci			goto out;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	pgdir = hns_roce_alloc_db_pgdir(hr_dev->dev);
13762306a36Sopenharmony_ci	if (!pgdir) {
13862306a36Sopenharmony_ci		ret = -ENOMEM;
13962306a36Sopenharmony_ci		goto out;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	list_add(&pgdir->list, &hr_dev->pgdir_list);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* This should never fail -- we just allocated an empty page: */
14562306a36Sopenharmony_ci	WARN_ON(hns_roce_alloc_db_from_pgdir(pgdir, db, order));
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ciout:
14862306a36Sopenharmony_ci	mutex_unlock(&hr_dev->pgdir_mutex);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return ret;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_civoid hns_roce_free_db(struct hns_roce_dev *hr_dev, struct hns_roce_db *db)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	unsigned long o;
15662306a36Sopenharmony_ci	unsigned long i;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	mutex_lock(&hr_dev->pgdir_mutex);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	o = db->order;
16162306a36Sopenharmony_ci	i = db->index;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	if (db->order == 0 && test_bit(i ^ 1, db->u.pgdir->order0)) {
16462306a36Sopenharmony_ci		clear_bit(i ^ 1, db->u.pgdir->order0);
16562306a36Sopenharmony_ci		++o;
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	i >>= o;
16962306a36Sopenharmony_ci	set_bit(i, db->u.pgdir->bits[o]);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	if (bitmap_full(db->u.pgdir->order1,
17262306a36Sopenharmony_ci			HNS_ROCE_DB_PER_PAGE / HNS_ROCE_DB_TYPE_COUNT)) {
17362306a36Sopenharmony_ci		dma_free_coherent(hr_dev->dev, PAGE_SIZE, db->u.pgdir->page,
17462306a36Sopenharmony_ci				  db->u.pgdir->db_dma);
17562306a36Sopenharmony_ci		list_del(&db->u.pgdir->list);
17662306a36Sopenharmony_ci		kfree(db->u.pgdir);
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	mutex_unlock(&hr_dev->pgdir_mutex);
18062306a36Sopenharmony_ci}
181