162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
462306a36Sopenharmony_ci *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  EMU10K1 memory page allocation (PTB area)
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/pci.h>
1062306a36Sopenharmony_ci#include <linux/gfp.h>
1162306a36Sopenharmony_ci#include <linux/time.h>
1262306a36Sopenharmony_ci#include <linux/mutex.h>
1362306a36Sopenharmony_ci#include <linux/export.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <sound/core.h>
1662306a36Sopenharmony_ci#include <sound/emu10k1.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* page arguments of these two macros are Emu page (4096 bytes), not like
1962306a36Sopenharmony_ci * aligned pages in others
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci#define __set_ptb_entry(emu,page,addr) \
2262306a36Sopenharmony_ci	(((__le32 *)(emu)->ptb_pages.area)[page] = \
2362306a36Sopenharmony_ci	 cpu_to_le32(((addr) << (emu->address_mode)) | (page)))
2462306a36Sopenharmony_ci#define __get_ptb_entry(emu, page) \
2562306a36Sopenharmony_ci	(le32_to_cpu(((__le32 *)(emu)->ptb_pages.area)[page]))
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define UNIT_PAGES		(PAGE_SIZE / EMUPAGESIZE)
2862306a36Sopenharmony_ci#define MAX_ALIGN_PAGES0		(MAXPAGES0 / UNIT_PAGES)
2962306a36Sopenharmony_ci#define MAX_ALIGN_PAGES1		(MAXPAGES1 / UNIT_PAGES)
3062306a36Sopenharmony_ci/* get aligned page from offset address */
3162306a36Sopenharmony_ci#define get_aligned_page(offset)	((offset) >> PAGE_SHIFT)
3262306a36Sopenharmony_ci/* get offset address from aligned page */
3362306a36Sopenharmony_ci#define aligned_page_offset(page)	((page) << PAGE_SHIFT)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#if PAGE_SIZE == EMUPAGESIZE && !IS_ENABLED(CONFIG_DYNAMIC_DEBUG)
3662306a36Sopenharmony_ci/* fill PTB entrie(s) corresponding to page with addr */
3762306a36Sopenharmony_ci#define set_ptb_entry(emu,page,addr)	__set_ptb_entry(emu,page,addr)
3862306a36Sopenharmony_ci/* fill PTB entrie(s) corresponding to page with silence pointer */
3962306a36Sopenharmony_ci#define set_silent_ptb(emu,page)	__set_ptb_entry(emu,page,emu->silent_page.addr)
4062306a36Sopenharmony_ci#else
4162306a36Sopenharmony_ci/* fill PTB entries -- we need to fill UNIT_PAGES entries */
4262306a36Sopenharmony_cistatic inline void set_ptb_entry(struct snd_emu10k1 *emu, int page, dma_addr_t addr)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	int i;
4562306a36Sopenharmony_ci	page *= UNIT_PAGES;
4662306a36Sopenharmony_ci	for (i = 0; i < UNIT_PAGES; i++, page++) {
4762306a36Sopenharmony_ci		__set_ptb_entry(emu, page, addr);
4862306a36Sopenharmony_ci		dev_dbg(emu->card->dev, "mapped page %d to entry %.8x\n", page,
4962306a36Sopenharmony_ci			(unsigned int)__get_ptb_entry(emu, page));
5062306a36Sopenharmony_ci		addr += EMUPAGESIZE;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_cistatic inline void set_silent_ptb(struct snd_emu10k1 *emu, int page)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	int i;
5662306a36Sopenharmony_ci	page *= UNIT_PAGES;
5762306a36Sopenharmony_ci	for (i = 0; i < UNIT_PAGES; i++, page++) {
5862306a36Sopenharmony_ci		/* do not increment ptr */
5962306a36Sopenharmony_ci		__set_ptb_entry(emu, page, emu->silent_page.addr);
6062306a36Sopenharmony_ci		dev_dbg(emu->card->dev, "mapped silent page %d to entry %.8x\n",
6162306a36Sopenharmony_ci			page, (unsigned int)__get_ptb_entry(emu, page));
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci#endif /* PAGE_SIZE */
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/*
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_cistatic int synth_alloc_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
7062306a36Sopenharmony_cistatic int synth_free_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define get_emu10k1_memblk(l,member)	list_entry(l, struct snd_emu10k1_memblk, member)
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/* initialize emu10k1 part */
7662306a36Sopenharmony_cistatic void emu10k1_memblk_init(struct snd_emu10k1_memblk *blk)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	blk->mapped_page = -1;
7962306a36Sopenharmony_ci	INIT_LIST_HEAD(&blk->mapped_link);
8062306a36Sopenharmony_ci	INIT_LIST_HEAD(&blk->mapped_order_link);
8162306a36Sopenharmony_ci	blk->map_locked = 0;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	blk->first_page = get_aligned_page(blk->mem.offset);
8462306a36Sopenharmony_ci	blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1);
8562306a36Sopenharmony_ci	blk->pages = blk->last_page - blk->first_page + 1;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * search empty region on PTB with the given size
9062306a36Sopenharmony_ci *
9162306a36Sopenharmony_ci * if an empty region is found, return the page and store the next mapped block
9262306a36Sopenharmony_ci * in nextp
9362306a36Sopenharmony_ci * if not found, return a negative error code.
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_cistatic int search_empty_map_area(struct snd_emu10k1 *emu, int npages, struct list_head **nextp)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	int page = 1, found_page = -ENOMEM;
9862306a36Sopenharmony_ci	int max_size = npages;
9962306a36Sopenharmony_ci	int size;
10062306a36Sopenharmony_ci	struct list_head *candidate = &emu->mapped_link_head;
10162306a36Sopenharmony_ci	struct list_head *pos;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	list_for_each (pos, &emu->mapped_link_head) {
10462306a36Sopenharmony_ci		struct snd_emu10k1_memblk *blk = get_emu10k1_memblk(pos, mapped_link);
10562306a36Sopenharmony_ci		if (blk->mapped_page < 0)
10662306a36Sopenharmony_ci			continue;
10762306a36Sopenharmony_ci		size = blk->mapped_page - page;
10862306a36Sopenharmony_ci		if (size == npages) {
10962306a36Sopenharmony_ci			*nextp = pos;
11062306a36Sopenharmony_ci			return page;
11162306a36Sopenharmony_ci		}
11262306a36Sopenharmony_ci		else if (size > max_size) {
11362306a36Sopenharmony_ci			/* we look for the maximum empty hole */
11462306a36Sopenharmony_ci			max_size = size;
11562306a36Sopenharmony_ci			candidate = pos;
11662306a36Sopenharmony_ci			found_page = page;
11762306a36Sopenharmony_ci		}
11862306a36Sopenharmony_ci		page = blk->mapped_page + blk->pages;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci	size = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0) - page;
12162306a36Sopenharmony_ci	if (size >= max_size) {
12262306a36Sopenharmony_ci		*nextp = pos;
12362306a36Sopenharmony_ci		return page;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci	*nextp = candidate;
12662306a36Sopenharmony_ci	return found_page;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/*
13062306a36Sopenharmony_ci * map a memory block onto emu10k1's PTB
13162306a36Sopenharmony_ci *
13262306a36Sopenharmony_ci * call with memblk_lock held
13362306a36Sopenharmony_ci */
13462306a36Sopenharmony_cistatic int map_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	int page, pg;
13762306a36Sopenharmony_ci	struct list_head *next;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	page = search_empty_map_area(emu, blk->pages, &next);
14062306a36Sopenharmony_ci	if (page < 0) /* not found */
14162306a36Sopenharmony_ci		return page;
14262306a36Sopenharmony_ci	if (page == 0) {
14362306a36Sopenharmony_ci		dev_err(emu->card->dev, "trying to map zero (reserved) page\n");
14462306a36Sopenharmony_ci		return -EINVAL;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci	/* insert this block in the proper position of mapped list */
14762306a36Sopenharmony_ci	list_add_tail(&blk->mapped_link, next);
14862306a36Sopenharmony_ci	/* append this as a newest block in order list */
14962306a36Sopenharmony_ci	list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
15062306a36Sopenharmony_ci	blk->mapped_page = page;
15162306a36Sopenharmony_ci	/* fill PTB */
15262306a36Sopenharmony_ci	for (pg = blk->first_page; pg <= blk->last_page; pg++) {
15362306a36Sopenharmony_ci		set_ptb_entry(emu, page, emu->page_addr_table[pg]);
15462306a36Sopenharmony_ci		page++;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci	return 0;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci/*
16062306a36Sopenharmony_ci * unmap the block
16162306a36Sopenharmony_ci * return the size of resultant empty pages
16262306a36Sopenharmony_ci *
16362306a36Sopenharmony_ci * call with memblk_lock held
16462306a36Sopenharmony_ci */
16562306a36Sopenharmony_cistatic int unmap_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	int start_page, end_page, mpage, pg;
16862306a36Sopenharmony_ci	struct list_head *p;
16962306a36Sopenharmony_ci	struct snd_emu10k1_memblk *q;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* calculate the expected size of empty region */
17262306a36Sopenharmony_ci	p = blk->mapped_link.prev;
17362306a36Sopenharmony_ci	if (p != &emu->mapped_link_head) {
17462306a36Sopenharmony_ci		q = get_emu10k1_memblk(p, mapped_link);
17562306a36Sopenharmony_ci		start_page = q->mapped_page + q->pages;
17662306a36Sopenharmony_ci	} else {
17762306a36Sopenharmony_ci		start_page = 1;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci	p = blk->mapped_link.next;
18062306a36Sopenharmony_ci	if (p != &emu->mapped_link_head) {
18162306a36Sopenharmony_ci		q = get_emu10k1_memblk(p, mapped_link);
18262306a36Sopenharmony_ci		end_page = q->mapped_page;
18362306a36Sopenharmony_ci	} else {
18462306a36Sopenharmony_ci		end_page = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0);
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* remove links */
18862306a36Sopenharmony_ci	list_del(&blk->mapped_link);
18962306a36Sopenharmony_ci	list_del(&blk->mapped_order_link);
19062306a36Sopenharmony_ci	/* clear PTB */
19162306a36Sopenharmony_ci	mpage = blk->mapped_page;
19262306a36Sopenharmony_ci	for (pg = blk->first_page; pg <= blk->last_page; pg++) {
19362306a36Sopenharmony_ci		set_silent_ptb(emu, mpage);
19462306a36Sopenharmony_ci		mpage++;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci	blk->mapped_page = -1;
19762306a36Sopenharmony_ci	return end_page - start_page; /* return the new empty size */
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci/*
20162306a36Sopenharmony_ci * search empty pages with the given size, and create a memory block
20262306a36Sopenharmony_ci *
20362306a36Sopenharmony_ci * unlike synth_alloc the memory block is aligned to the page start
20462306a36Sopenharmony_ci */
20562306a36Sopenharmony_cistatic struct snd_emu10k1_memblk *
20662306a36Sopenharmony_cisearch_empty(struct snd_emu10k1 *emu, int size)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	struct list_head *p;
20962306a36Sopenharmony_ci	struct snd_emu10k1_memblk *blk;
21062306a36Sopenharmony_ci	int page, psize;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	psize = get_aligned_page(size + PAGE_SIZE -1);
21362306a36Sopenharmony_ci	page = 0;
21462306a36Sopenharmony_ci	list_for_each(p, &emu->memhdr->block) {
21562306a36Sopenharmony_ci		blk = get_emu10k1_memblk(p, mem.list);
21662306a36Sopenharmony_ci		if (page + psize <= blk->first_page)
21762306a36Sopenharmony_ci			goto __found_pages;
21862306a36Sopenharmony_ci		page = blk->last_page + 1;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci	if (page + psize > emu->max_cache_pages)
22162306a36Sopenharmony_ci		return NULL;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci__found_pages:
22462306a36Sopenharmony_ci	/* create a new memory block */
22562306a36Sopenharmony_ci	blk = (struct snd_emu10k1_memblk *)__snd_util_memblk_new(emu->memhdr, psize << PAGE_SHIFT, p->prev);
22662306a36Sopenharmony_ci	if (blk == NULL)
22762306a36Sopenharmony_ci		return NULL;
22862306a36Sopenharmony_ci	blk->mem.offset = aligned_page_offset(page); /* set aligned offset */
22962306a36Sopenharmony_ci	emu10k1_memblk_init(blk);
23062306a36Sopenharmony_ci	return blk;
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci/*
23562306a36Sopenharmony_ci * check if the given pointer is valid for pages
23662306a36Sopenharmony_ci */
23762306a36Sopenharmony_cistatic int is_valid_page(struct snd_emu10k1 *emu, dma_addr_t addr)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	if (addr & ~emu->dma_mask) {
24062306a36Sopenharmony_ci		dev_err_ratelimited(emu->card->dev,
24162306a36Sopenharmony_ci			"max memory size is 0x%lx (addr = 0x%lx)!!\n",
24262306a36Sopenharmony_ci			emu->dma_mask, (unsigned long)addr);
24362306a36Sopenharmony_ci		return 0;
24462306a36Sopenharmony_ci	}
24562306a36Sopenharmony_ci	if (addr & (EMUPAGESIZE-1)) {
24662306a36Sopenharmony_ci		dev_err_ratelimited(emu->card->dev, "page is not aligned\n");
24762306a36Sopenharmony_ci		return 0;
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci	return 1;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci/*
25362306a36Sopenharmony_ci * map the given memory block on PTB.
25462306a36Sopenharmony_ci * if the block is already mapped, update the link order.
25562306a36Sopenharmony_ci * if no empty pages are found, tries to release unused memory blocks
25662306a36Sopenharmony_ci * and retry the mapping.
25762306a36Sopenharmony_ci */
25862306a36Sopenharmony_ciint snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	int err;
26162306a36Sopenharmony_ci	int size;
26262306a36Sopenharmony_ci	struct list_head *p, *nextp;
26362306a36Sopenharmony_ci	struct snd_emu10k1_memblk *deleted;
26462306a36Sopenharmony_ci	unsigned long flags;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	spin_lock_irqsave(&emu->memblk_lock, flags);
26762306a36Sopenharmony_ci	if (blk->mapped_page >= 0) {
26862306a36Sopenharmony_ci		/* update order link */
26962306a36Sopenharmony_ci		list_move_tail(&blk->mapped_order_link,
27062306a36Sopenharmony_ci			       &emu->mapped_order_link_head);
27162306a36Sopenharmony_ci		spin_unlock_irqrestore(&emu->memblk_lock, flags);
27262306a36Sopenharmony_ci		return 0;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci	err = map_memblk(emu, blk);
27562306a36Sopenharmony_ci	if (err < 0) {
27662306a36Sopenharmony_ci		/* no enough page - try to unmap some blocks */
27762306a36Sopenharmony_ci		/* starting from the oldest block */
27862306a36Sopenharmony_ci		p = emu->mapped_order_link_head.next;
27962306a36Sopenharmony_ci		for (; p != &emu->mapped_order_link_head; p = nextp) {
28062306a36Sopenharmony_ci			nextp = p->next;
28162306a36Sopenharmony_ci			deleted = get_emu10k1_memblk(p, mapped_order_link);
28262306a36Sopenharmony_ci			if (deleted->map_locked)
28362306a36Sopenharmony_ci				continue;
28462306a36Sopenharmony_ci			size = unmap_memblk(emu, deleted);
28562306a36Sopenharmony_ci			if (size >= blk->pages) {
28662306a36Sopenharmony_ci				/* ok the empty region is enough large */
28762306a36Sopenharmony_ci				err = map_memblk(emu, blk);
28862306a36Sopenharmony_ci				break;
28962306a36Sopenharmony_ci			}
29062306a36Sopenharmony_ci		}
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci	spin_unlock_irqrestore(&emu->memblk_lock, flags);
29362306a36Sopenharmony_ci	return err;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_memblk_map);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci/*
29962306a36Sopenharmony_ci * page allocation for DMA
30062306a36Sopenharmony_ci */
30162306a36Sopenharmony_cistruct snd_util_memblk *
30262306a36Sopenharmony_cisnd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
30562306a36Sopenharmony_ci	struct snd_util_memhdr *hdr;
30662306a36Sopenharmony_ci	struct snd_emu10k1_memblk *blk;
30762306a36Sopenharmony_ci	int page, err, idx;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (snd_BUG_ON(!emu))
31062306a36Sopenharmony_ci		return NULL;
31162306a36Sopenharmony_ci	if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
31262306a36Sopenharmony_ci		       runtime->dma_bytes >= (emu->address_mode ? MAXPAGES1 : MAXPAGES0) * EMUPAGESIZE))
31362306a36Sopenharmony_ci		return NULL;
31462306a36Sopenharmony_ci	hdr = emu->memhdr;
31562306a36Sopenharmony_ci	if (snd_BUG_ON(!hdr))
31662306a36Sopenharmony_ci		return NULL;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
31962306a36Sopenharmony_ci	blk = search_empty(emu, runtime->dma_bytes);
32062306a36Sopenharmony_ci	if (blk == NULL) {
32162306a36Sopenharmony_ci		mutex_unlock(&hdr->block_mutex);
32262306a36Sopenharmony_ci		return NULL;
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci	/* fill buffer addresses but pointers are not stored so that
32562306a36Sopenharmony_ci	 * snd_free_pci_page() is not called in synth_free()
32662306a36Sopenharmony_ci	 */
32762306a36Sopenharmony_ci	idx = 0;
32862306a36Sopenharmony_ci	for (page = blk->first_page; page <= blk->last_page; page++, idx++) {
32962306a36Sopenharmony_ci		unsigned long ofs = idx << PAGE_SHIFT;
33062306a36Sopenharmony_ci		dma_addr_t addr;
33162306a36Sopenharmony_ci		if (ofs >= runtime->dma_bytes)
33262306a36Sopenharmony_ci			addr = emu->silent_page.addr;
33362306a36Sopenharmony_ci		else
33462306a36Sopenharmony_ci			addr = snd_pcm_sgbuf_get_addr(substream, ofs);
33562306a36Sopenharmony_ci		if (! is_valid_page(emu, addr)) {
33662306a36Sopenharmony_ci			dev_err_ratelimited(emu->card->dev,
33762306a36Sopenharmony_ci				"emu: failure page = %d\n", idx);
33862306a36Sopenharmony_ci			mutex_unlock(&hdr->block_mutex);
33962306a36Sopenharmony_ci			return NULL;
34062306a36Sopenharmony_ci		}
34162306a36Sopenharmony_ci		emu->page_addr_table[page] = addr;
34262306a36Sopenharmony_ci		emu->page_ptr_table[page] = NULL;
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* set PTB entries */
34662306a36Sopenharmony_ci	blk->map_locked = 1; /* do not unmap this block! */
34762306a36Sopenharmony_ci	err = snd_emu10k1_memblk_map(emu, blk);
34862306a36Sopenharmony_ci	if (err < 0) {
34962306a36Sopenharmony_ci		__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
35062306a36Sopenharmony_ci		mutex_unlock(&hdr->block_mutex);
35162306a36Sopenharmony_ci		return NULL;
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
35462306a36Sopenharmony_ci	return (struct snd_util_memblk *)blk;
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci/*
35962306a36Sopenharmony_ci * release DMA buffer from page table
36062306a36Sopenharmony_ci */
36162306a36Sopenharmony_ciint snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk)
36262306a36Sopenharmony_ci{
36362306a36Sopenharmony_ci	if (snd_BUG_ON(!emu || !blk))
36462306a36Sopenharmony_ci		return -EINVAL;
36562306a36Sopenharmony_ci	return snd_emu10k1_synth_free(emu, blk);
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci/*
36962306a36Sopenharmony_ci * allocate DMA pages, widening the allocation if necessary
37062306a36Sopenharmony_ci *
37162306a36Sopenharmony_ci * See the comment above snd_emu10k1_detect_iommu() in emu10k1_main.c why
37262306a36Sopenharmony_ci * this might be needed.
37362306a36Sopenharmony_ci *
37462306a36Sopenharmony_ci * If you modify this function check whether __synth_free_pages() also needs
37562306a36Sopenharmony_ci * changes.
37662306a36Sopenharmony_ci */
37762306a36Sopenharmony_ciint snd_emu10k1_alloc_pages_maybe_wider(struct snd_emu10k1 *emu, size_t size,
37862306a36Sopenharmony_ci					struct snd_dma_buffer *dmab)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	if (emu->iommu_workaround) {
38162306a36Sopenharmony_ci		size_t npages = DIV_ROUND_UP(size, PAGE_SIZE);
38262306a36Sopenharmony_ci		size_t size_real = npages * PAGE_SIZE;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci		/*
38562306a36Sopenharmony_ci		 * The device has been observed to accesses up to 256 extra
38662306a36Sopenharmony_ci		 * bytes, but use 1k to be safe.
38762306a36Sopenharmony_ci		 */
38862306a36Sopenharmony_ci		if (size_real < size + 1024)
38962306a36Sopenharmony_ci			size += PAGE_SIZE;
39062306a36Sopenharmony_ci	}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	return snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
39362306a36Sopenharmony_ci				   &emu->pci->dev, size, dmab);
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci/*
39762306a36Sopenharmony_ci * memory allocation using multiple pages (for synth)
39862306a36Sopenharmony_ci * Unlike the DMA allocation above, non-contiguous pages are assined.
39962306a36Sopenharmony_ci */
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci/*
40262306a36Sopenharmony_ci * allocate a synth sample area
40362306a36Sopenharmony_ci */
40462306a36Sopenharmony_cistruct snd_util_memblk *
40562306a36Sopenharmony_cisnd_emu10k1_synth_alloc(struct snd_emu10k1 *hw, unsigned int size)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	struct snd_emu10k1_memblk *blk;
40862306a36Sopenharmony_ci	struct snd_util_memhdr *hdr = hw->memhdr;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
41162306a36Sopenharmony_ci	blk = (struct snd_emu10k1_memblk *)__snd_util_mem_alloc(hdr, size);
41262306a36Sopenharmony_ci	if (blk == NULL) {
41362306a36Sopenharmony_ci		mutex_unlock(&hdr->block_mutex);
41462306a36Sopenharmony_ci		return NULL;
41562306a36Sopenharmony_ci	}
41662306a36Sopenharmony_ci	if (synth_alloc_pages(hw, blk)) {
41762306a36Sopenharmony_ci		__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
41862306a36Sopenharmony_ci		mutex_unlock(&hdr->block_mutex);
41962306a36Sopenharmony_ci		return NULL;
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci	snd_emu10k1_memblk_map(hw, blk);
42262306a36Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
42362306a36Sopenharmony_ci	return (struct snd_util_memblk *)blk;
42462306a36Sopenharmony_ci}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_synth_alloc);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci/*
42962306a36Sopenharmony_ci * free a synth sample area
43062306a36Sopenharmony_ci */
43162306a36Sopenharmony_ciint
43262306a36Sopenharmony_cisnd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *memblk)
43362306a36Sopenharmony_ci{
43462306a36Sopenharmony_ci	struct snd_util_memhdr *hdr = emu->memhdr;
43562306a36Sopenharmony_ci	struct snd_emu10k1_memblk *blk = (struct snd_emu10k1_memblk *)memblk;
43662306a36Sopenharmony_ci	unsigned long flags;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	mutex_lock(&hdr->block_mutex);
43962306a36Sopenharmony_ci	spin_lock_irqsave(&emu->memblk_lock, flags);
44062306a36Sopenharmony_ci	if (blk->mapped_page >= 0)
44162306a36Sopenharmony_ci		unmap_memblk(emu, blk);
44262306a36Sopenharmony_ci	spin_unlock_irqrestore(&emu->memblk_lock, flags);
44362306a36Sopenharmony_ci	synth_free_pages(emu, blk);
44462306a36Sopenharmony_ci	 __snd_util_mem_free(hdr, memblk);
44562306a36Sopenharmony_ci	mutex_unlock(&hdr->block_mutex);
44662306a36Sopenharmony_ci	return 0;
44762306a36Sopenharmony_ci}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_synth_free);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci/* check new allocation range */
45262306a36Sopenharmony_cistatic void get_single_page_range(struct snd_util_memhdr *hdr,
45362306a36Sopenharmony_ci				  struct snd_emu10k1_memblk *blk,
45462306a36Sopenharmony_ci				  int *first_page_ret, int *last_page_ret)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	struct list_head *p;
45762306a36Sopenharmony_ci	struct snd_emu10k1_memblk *q;
45862306a36Sopenharmony_ci	int first_page, last_page;
45962306a36Sopenharmony_ci	first_page = blk->first_page;
46062306a36Sopenharmony_ci	p = blk->mem.list.prev;
46162306a36Sopenharmony_ci	if (p != &hdr->block) {
46262306a36Sopenharmony_ci		q = get_emu10k1_memblk(p, mem.list);
46362306a36Sopenharmony_ci		if (q->last_page == first_page)
46462306a36Sopenharmony_ci			first_page++;  /* first page was already allocated */
46562306a36Sopenharmony_ci	}
46662306a36Sopenharmony_ci	last_page = blk->last_page;
46762306a36Sopenharmony_ci	p = blk->mem.list.next;
46862306a36Sopenharmony_ci	if (p != &hdr->block) {
46962306a36Sopenharmony_ci		q = get_emu10k1_memblk(p, mem.list);
47062306a36Sopenharmony_ci		if (q->first_page == last_page)
47162306a36Sopenharmony_ci			last_page--; /* last page was already allocated */
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci	*first_page_ret = first_page;
47462306a36Sopenharmony_ci	*last_page_ret = last_page;
47562306a36Sopenharmony_ci}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci/* release allocated pages */
47862306a36Sopenharmony_cistatic void __synth_free_pages(struct snd_emu10k1 *emu, int first_page,
47962306a36Sopenharmony_ci			       int last_page)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	struct snd_dma_buffer dmab;
48262306a36Sopenharmony_ci	int page;
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	dmab.dev.type = SNDRV_DMA_TYPE_DEV;
48562306a36Sopenharmony_ci	dmab.dev.dev = &emu->pci->dev;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	for (page = first_page; page <= last_page; page++) {
48862306a36Sopenharmony_ci		if (emu->page_ptr_table[page] == NULL)
48962306a36Sopenharmony_ci			continue;
49062306a36Sopenharmony_ci		dmab.area = emu->page_ptr_table[page];
49162306a36Sopenharmony_ci		dmab.addr = emu->page_addr_table[page];
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci		/*
49462306a36Sopenharmony_ci		 * please keep me in sync with logic in
49562306a36Sopenharmony_ci		 * snd_emu10k1_alloc_pages_maybe_wider()
49662306a36Sopenharmony_ci		 */
49762306a36Sopenharmony_ci		dmab.bytes = PAGE_SIZE;
49862306a36Sopenharmony_ci		if (emu->iommu_workaround)
49962306a36Sopenharmony_ci			dmab.bytes *= 2;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		snd_dma_free_pages(&dmab);
50262306a36Sopenharmony_ci		emu->page_addr_table[page] = 0;
50362306a36Sopenharmony_ci		emu->page_ptr_table[page] = NULL;
50462306a36Sopenharmony_ci	}
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci/*
50862306a36Sopenharmony_ci * allocate kernel pages
50962306a36Sopenharmony_ci */
51062306a36Sopenharmony_cistatic int synth_alloc_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	int page, first_page, last_page;
51362306a36Sopenharmony_ci	struct snd_dma_buffer dmab;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	emu10k1_memblk_init(blk);
51662306a36Sopenharmony_ci	get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
51762306a36Sopenharmony_ci	/* allocate kernel pages */
51862306a36Sopenharmony_ci	for (page = first_page; page <= last_page; page++) {
51962306a36Sopenharmony_ci		if (snd_emu10k1_alloc_pages_maybe_wider(emu, PAGE_SIZE,
52062306a36Sopenharmony_ci							&dmab) < 0)
52162306a36Sopenharmony_ci			goto __fail;
52262306a36Sopenharmony_ci		if (!is_valid_page(emu, dmab.addr)) {
52362306a36Sopenharmony_ci			snd_dma_free_pages(&dmab);
52462306a36Sopenharmony_ci			goto __fail;
52562306a36Sopenharmony_ci		}
52662306a36Sopenharmony_ci		emu->page_addr_table[page] = dmab.addr;
52762306a36Sopenharmony_ci		emu->page_ptr_table[page] = dmab.area;
52862306a36Sopenharmony_ci	}
52962306a36Sopenharmony_ci	return 0;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci__fail:
53262306a36Sopenharmony_ci	/* release allocated pages */
53362306a36Sopenharmony_ci	last_page = page - 1;
53462306a36Sopenharmony_ci	__synth_free_pages(emu, first_page, last_page);
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	return -ENOMEM;
53762306a36Sopenharmony_ci}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci/*
54062306a36Sopenharmony_ci * free pages
54162306a36Sopenharmony_ci */
54262306a36Sopenharmony_cistatic int synth_free_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
54362306a36Sopenharmony_ci{
54462306a36Sopenharmony_ci	int first_page, last_page;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
54762306a36Sopenharmony_ci	__synth_free_pages(emu, first_page, last_page);
54862306a36Sopenharmony_ci	return 0;
54962306a36Sopenharmony_ci}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci/* calculate buffer pointer from offset address */
55262306a36Sopenharmony_cistatic inline void *offset_ptr(struct snd_emu10k1 *emu, int page, int offset)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	char *ptr;
55562306a36Sopenharmony_ci	if (snd_BUG_ON(page < 0 || page >= emu->max_cache_pages))
55662306a36Sopenharmony_ci		return NULL;
55762306a36Sopenharmony_ci	ptr = emu->page_ptr_table[page];
55862306a36Sopenharmony_ci	if (! ptr) {
55962306a36Sopenharmony_ci		dev_err(emu->card->dev,
56062306a36Sopenharmony_ci			"access to NULL ptr: page = %d\n", page);
56162306a36Sopenharmony_ci		return NULL;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci	ptr += offset & (PAGE_SIZE - 1);
56462306a36Sopenharmony_ci	return (void*)ptr;
56562306a36Sopenharmony_ci}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci/*
56862306a36Sopenharmony_ci * bzero(blk + offset, size)
56962306a36Sopenharmony_ci */
57062306a36Sopenharmony_ciint snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
57162306a36Sopenharmony_ci			    int offset, int size)
57262306a36Sopenharmony_ci{
57362306a36Sopenharmony_ci	int page, nextofs, end_offset, temp, temp1;
57462306a36Sopenharmony_ci	void *ptr;
57562306a36Sopenharmony_ci	struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	offset += blk->offset & (PAGE_SIZE - 1);
57862306a36Sopenharmony_ci	end_offset = offset + size;
57962306a36Sopenharmony_ci	page = get_aligned_page(offset);
58062306a36Sopenharmony_ci	do {
58162306a36Sopenharmony_ci		nextofs = aligned_page_offset(page + 1);
58262306a36Sopenharmony_ci		temp = nextofs - offset;
58362306a36Sopenharmony_ci		temp1 = end_offset - offset;
58462306a36Sopenharmony_ci		if (temp1 < temp)
58562306a36Sopenharmony_ci			temp = temp1;
58662306a36Sopenharmony_ci		ptr = offset_ptr(emu, page + p->first_page, offset);
58762306a36Sopenharmony_ci		if (ptr)
58862306a36Sopenharmony_ci			memset(ptr, 0, temp);
58962306a36Sopenharmony_ci		offset = nextofs;
59062306a36Sopenharmony_ci		page++;
59162306a36Sopenharmony_ci	} while (offset < end_offset);
59262306a36Sopenharmony_ci	return 0;
59362306a36Sopenharmony_ci}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_synth_bzero);
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci/*
59862306a36Sopenharmony_ci * copy_from_user(blk + offset, data, size)
59962306a36Sopenharmony_ci */
60062306a36Sopenharmony_ciint snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
60162306a36Sopenharmony_ci				     int offset, const char __user *data, int size)
60262306a36Sopenharmony_ci{
60362306a36Sopenharmony_ci	int page, nextofs, end_offset, temp, temp1;
60462306a36Sopenharmony_ci	void *ptr;
60562306a36Sopenharmony_ci	struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	offset += blk->offset & (PAGE_SIZE - 1);
60862306a36Sopenharmony_ci	end_offset = offset + size;
60962306a36Sopenharmony_ci	page = get_aligned_page(offset);
61062306a36Sopenharmony_ci	do {
61162306a36Sopenharmony_ci		nextofs = aligned_page_offset(page + 1);
61262306a36Sopenharmony_ci		temp = nextofs - offset;
61362306a36Sopenharmony_ci		temp1 = end_offset - offset;
61462306a36Sopenharmony_ci		if (temp1 < temp)
61562306a36Sopenharmony_ci			temp = temp1;
61662306a36Sopenharmony_ci		ptr = offset_ptr(emu, page + p->first_page, offset);
61762306a36Sopenharmony_ci		if (ptr && copy_from_user(ptr, data, temp))
61862306a36Sopenharmony_ci			return -EFAULT;
61962306a36Sopenharmony_ci		offset = nextofs;
62062306a36Sopenharmony_ci		data += temp;
62162306a36Sopenharmony_ci		page++;
62262306a36Sopenharmony_ci	} while (offset < end_offset);
62362306a36Sopenharmony_ci	return 0;
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user);
627