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