18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Generic memory management routines for soundcard memory allocation
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/mutex.h>
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <sound/core.h>
138c2ecf20Sopenharmony_ci#include <sound/util_mem.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Takashi Iwai");
168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Generic memory management routines for soundcard memory allocation");
178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define get_memblk(p)	list_entry(p, struct snd_util_memblk, list)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/*
228c2ecf20Sopenharmony_ci * create a new memory manager
238c2ecf20Sopenharmony_ci */
248c2ecf20Sopenharmony_cistruct snd_util_memhdr *
258c2ecf20Sopenharmony_cisnd_util_memhdr_new(int memsize)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	struct snd_util_memhdr *hdr;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
308c2ecf20Sopenharmony_ci	if (hdr == NULL)
318c2ecf20Sopenharmony_ci		return NULL;
328c2ecf20Sopenharmony_ci	hdr->size = memsize;
338c2ecf20Sopenharmony_ci	mutex_init(&hdr->block_mutex);
348c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&hdr->block);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	return hdr;
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/*
408c2ecf20Sopenharmony_ci * free a memory manager
418c2ecf20Sopenharmony_ci */
428c2ecf20Sopenharmony_civoid snd_util_memhdr_free(struct snd_util_memhdr *hdr)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct list_head *p;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (!hdr)
478c2ecf20Sopenharmony_ci		return;
488c2ecf20Sopenharmony_ci	/* release all blocks */
498c2ecf20Sopenharmony_ci	while ((p = hdr->block.next) != &hdr->block) {
508c2ecf20Sopenharmony_ci		list_del(p);
518c2ecf20Sopenharmony_ci		kfree(get_memblk(p));
528c2ecf20Sopenharmony_ci	}
538c2ecf20Sopenharmony_ci	kfree(hdr);
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci/*
578c2ecf20Sopenharmony_ci * allocate a memory block (without mutex)
588c2ecf20Sopenharmony_ci */
598c2ecf20Sopenharmony_cistruct snd_util_memblk *
608c2ecf20Sopenharmony_ci__snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct snd_util_memblk *blk;
638c2ecf20Sopenharmony_ci	unsigned int units, prev_offset;
648c2ecf20Sopenharmony_ci	struct list_head *p;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!hdr || size <= 0))
678c2ecf20Sopenharmony_ci		return NULL;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/* word alignment */
708c2ecf20Sopenharmony_ci	units = size;
718c2ecf20Sopenharmony_ci	if (units & 1)
728c2ecf20Sopenharmony_ci		units++;
738c2ecf20Sopenharmony_ci	if (units > hdr->size)
748c2ecf20Sopenharmony_ci		return NULL;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	/* look for empty block */
778c2ecf20Sopenharmony_ci	prev_offset = 0;
788c2ecf20Sopenharmony_ci	list_for_each(p, &hdr->block) {
798c2ecf20Sopenharmony_ci		blk = get_memblk(p);
808c2ecf20Sopenharmony_ci		if (blk->offset - prev_offset >= units)
818c2ecf20Sopenharmony_ci			goto __found;
828c2ecf20Sopenharmony_ci		prev_offset = blk->offset + blk->size;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci	if (hdr->size - prev_offset < units)
858c2ecf20Sopenharmony_ci		return NULL;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci__found:
888c2ecf20Sopenharmony_ci	return __snd_util_memblk_new(hdr, units, p->prev);
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci/*
938c2ecf20Sopenharmony_ci * create a new memory block with the given size
948c2ecf20Sopenharmony_ci * the block is linked next to prev
958c2ecf20Sopenharmony_ci */
968c2ecf20Sopenharmony_cistruct snd_util_memblk *
978c2ecf20Sopenharmony_ci__snd_util_memblk_new(struct snd_util_memhdr *hdr, unsigned int units,
988c2ecf20Sopenharmony_ci		      struct list_head *prev)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	struct snd_util_memblk *blk;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	blk = kmalloc(sizeof(struct snd_util_memblk) + hdr->block_extra_size,
1038c2ecf20Sopenharmony_ci		      GFP_KERNEL);
1048c2ecf20Sopenharmony_ci	if (blk == NULL)
1058c2ecf20Sopenharmony_ci		return NULL;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	if (prev == &hdr->block)
1088c2ecf20Sopenharmony_ci		blk->offset = 0;
1098c2ecf20Sopenharmony_ci	else {
1108c2ecf20Sopenharmony_ci		struct snd_util_memblk *p = get_memblk(prev);
1118c2ecf20Sopenharmony_ci		blk->offset = p->offset + p->size;
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci	blk->size = units;
1148c2ecf20Sopenharmony_ci	list_add(&blk->list, prev);
1158c2ecf20Sopenharmony_ci	hdr->nblocks++;
1168c2ecf20Sopenharmony_ci	hdr->used += units;
1178c2ecf20Sopenharmony_ci	return blk;
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci/*
1228c2ecf20Sopenharmony_ci * allocate a memory block (with mutex)
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistruct snd_util_memblk *
1258c2ecf20Sopenharmony_cisnd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct snd_util_memblk *blk;
1288c2ecf20Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
1298c2ecf20Sopenharmony_ci	blk = __snd_util_mem_alloc(hdr, size);
1308c2ecf20Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
1318c2ecf20Sopenharmony_ci	return blk;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci/*
1368c2ecf20Sopenharmony_ci * remove the block from linked-list and free resource
1378c2ecf20Sopenharmony_ci * (without mutex)
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_civoid
1408c2ecf20Sopenharmony_ci__snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	list_del(&blk->list);
1438c2ecf20Sopenharmony_ci	hdr->nblocks--;
1448c2ecf20Sopenharmony_ci	hdr->used -= blk->size;
1458c2ecf20Sopenharmony_ci	kfree(blk);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci/*
1498c2ecf20Sopenharmony_ci * free a memory block (with mutex)
1508c2ecf20Sopenharmony_ci */
1518c2ecf20Sopenharmony_ciint snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!hdr || !blk))
1548c2ecf20Sopenharmony_ci		return -EINVAL;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
1578c2ecf20Sopenharmony_ci	__snd_util_mem_free(hdr, blk);
1588c2ecf20Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
1598c2ecf20Sopenharmony_ci	return 0;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci/*
1638c2ecf20Sopenharmony_ci * return available memory size
1648c2ecf20Sopenharmony_ci */
1658c2ecf20Sopenharmony_ciint snd_util_mem_avail(struct snd_util_memhdr *hdr)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	unsigned int size;
1688c2ecf20Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
1698c2ecf20Sopenharmony_ci	size = hdr->size - hdr->used;
1708c2ecf20Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
1718c2ecf20Sopenharmony_ci	return size;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_util_memhdr_new);
1768c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_util_memhdr_free);
1778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_util_mem_alloc);
1788c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_util_mem_free);
1798c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_util_mem_avail);
1808c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__snd_util_mem_alloc);
1818c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__snd_util_mem_free);
1828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__snd_util_memblk_new);
183