📄 sba_iommu.c
字号:
ioc->res_bitshift = 0; } else { ioc->res_hint = res_ptr; } return (pide);}/** * sba_alloc_range - find free bits and mark them in IO PDIR resource bitmap * @ioc: IO MMU structure which owns the pdir we are interested in. * @size: number of bytes to create a mapping for * * Given a size, find consecutive unmarked and then mark those bits in the * resource bit map. */static intsba_alloc_range(struct ioc *ioc, size_t size){ unsigned int pages_needed = size >> IOVP_SHIFT;#ifdef CONFIG_PROC_FS unsigned long itc_start = ia64_get_itc();#endif unsigned long pide; ASSERT(pages_needed); ASSERT((pages_needed * IOVP_SIZE) <= DMA_CHUNK_SIZE); ASSERT(pages_needed <= BITS_PER_LONG); ASSERT(0 == (size & ~IOVP_MASK)); /* ** "seek and ye shall find"...praying never hurts either... */ pide = sba_search_bitmap(ioc, pages_needed); if (pide >= (ioc->res_size << 3)) { pide = sba_search_bitmap(ioc, pages_needed); if (pide >= (ioc->res_size << 3)) panic(__FILE__ ": I/O MMU @ %lx is out of mapping resources\n", ioc->ioc_hpa); }#ifdef ASSERT_PDIR_SANITY /* verify the first enable bit is clear */ if(0x00 != ((u8 *) ioc->pdir_base)[pide*sizeof(u64) + 7]) { sba_dump_pdir_entry(ioc, "sba_search_bitmap() botched it?", pide); }#endif DBG_RES("%s(%x) %d -> %lx hint %x/%x\n", __FUNCTION__, size, pages_needed, pide, (uint) ((unsigned long) ioc->res_hint - (unsigned long) ioc->res_map), ioc->res_bitshift );#ifdef CONFIG_PROC_FS { unsigned long itc_end = ia64_get_itc(); unsigned long tmp = itc_end - itc_start; /* check for roll over */ itc_start = (itc_end < itc_start) ? -(tmp) : (tmp); } ioc->avg_search[ioc->avg_idx++] = itc_start; ioc->avg_idx &= SBA_SEARCH_SAMPLE - 1; ioc->used_pages += pages_needed;#endif return (pide);}/** * sba_free_range - unmark bits in IO PDIR resource bitmap * @ioc: IO MMU structure which owns the pdir we are interested in. * @iova: IO virtual address which was previously allocated. * @size: number of bytes to create a mapping for * * clear bits in the ioc's resource map */static SBA_INLINE voidsba_free_range(struct ioc *ioc, dma_addr_t iova, size_t size){ unsigned long iovp = SBA_IOVP(ioc, iova); unsigned int pide = PDIR_INDEX(iovp); unsigned int ridx = pide >> 3; /* convert bit to byte address */ unsigned long *res_ptr = (unsigned long *) &((ioc)->res_map[ridx & ~RESMAP_IDX_MASK]); int bits_not_wanted = size >> IOVP_SHIFT; /* 3-bits "bit" address plus 2 (or 3) bits for "byte" == bit in word */ unsigned long m = RESMAP_MASK(bits_not_wanted) << (pide & (BITS_PER_LONG - 1)); DBG_RES("%s( ,%x,%x) %x/%lx %x %p %lx\n", __FUNCTION__, (uint) iova, size, bits_not_wanted, m, pide, res_ptr, *res_ptr);#ifdef CONFIG_PROC_FS ioc->used_pages -= bits_not_wanted;#endif ASSERT(m != 0); ASSERT(bits_not_wanted); ASSERT((bits_not_wanted * IOVP_SIZE) <= DMA_CHUNK_SIZE); ASSERT(bits_not_wanted <= BITS_PER_LONG); ASSERT((*res_ptr & m) == m); /* verify same bits are set */ *res_ptr &= ~m;}/**************************************************************** "Dynamic DMA Mapping" support (aka "Coherent I/O")****************************************************************/#define SBA_DMA_HINT(ioc, val) ((val) << (ioc)->hint_shift_pdir)/** * sba_io_pdir_entry - fill in one IO PDIR entry * @pdir_ptr: pointer to IO PDIR entry * @vba: Virtual CPU address of buffer to map * * SBA Mapping Routine * * Given a virtual address (vba, arg1) sba_io_pdir_entry() * loads the I/O PDIR entry pointed to by pdir_ptr (arg0). * Each IO Pdir entry consists of 8 bytes as shown below * (LSB == bit 0): * * 63 40 11 7 0 * +-+---------------------+----------------------------------+----+--------+ * |V| U | PPN[39:12] | U | FF | * +-+---------------------+----------------------------------+----+--------+ * * V == Valid Bit * U == Unused * PPN == Physical Page Number * * The physical address fields are filled with the results of virt_to_phys() * on the vba. */#if 1#define sba_io_pdir_entry(pdir_ptr, vba) *pdir_ptr = ((vba & ~0xE000000000000FFFULL) | 0x8000000000000000ULL)#elsevoid SBA_INLINEsba_io_pdir_entry(u64 *pdir_ptr, unsigned long vba){ *pdir_ptr = ((vba & ~0xE000000000000FFFULL) | 0x80000000000000FFULL);}#endif#ifdef ENABLE_MARK_CLEAN/** * Since DMA is i-cache coherent, any (complete) pages that were written via * DMA can be marked as "clean" so that update_mmu_cache() doesn't have to * flush them when they get mapped into an executable vm-area. */static voidmark_clean (void *addr, size_t size){ unsigned long pg_addr, end; pg_addr = PAGE_ALIGN((unsigned long) addr); end = (unsigned long) addr + size; while (pg_addr + PAGE_SIZE <= end) { struct page *page = virt_to_page((void *)pg_addr); set_bit(PG_arch_1, &page->flags); pg_addr += PAGE_SIZE; }}#endif/** * sba_mark_invalid - invalidate one or more IO PDIR entries * @ioc: IO MMU structure which owns the pdir we are interested in. * @iova: IO Virtual Address mapped earlier * @byte_cnt: number of bytes this mapping covers. * * Marking the IO PDIR entry(ies) as Invalid and invalidate * corresponding IO TLB entry. The PCOM (Purge Command Register) * is to purge stale entries in the IO TLB when unmapping entries. * * The PCOM register supports purging of multiple pages, with a minium * of 1 page and a maximum of 2GB. Hardware requires the address be * aligned to the size of the range being purged. The size of the range * must be a power of 2. The "Cool perf optimization" in the * allocation routine helps keep that true. */static SBA_INLINE voidsba_mark_invalid(struct ioc *ioc, dma_addr_t iova, size_t byte_cnt){ u32 iovp = (u32) SBA_IOVP(ioc,iova); int off = PDIR_INDEX(iovp); /* Must be non-zero and rounded up */ ASSERT(byte_cnt > 0); ASSERT(0 == (byte_cnt & ~IOVP_MASK));#ifdef ASSERT_PDIR_SANITY /* Assert first pdir entry is set */ if (!(ioc->pdir_base[off] >> 60)) { sba_dump_pdir_entry(ioc,"sba_mark_invalid()", PDIR_INDEX(iovp)); }#endif if (byte_cnt <= IOVP_SIZE) { ASSERT(off < ioc->pdir_size); iovp |= IOVP_SHIFT; /* set "size" field for PCOM */#ifndef FULL_VALID_PDIR /* ** clear I/O PDIR entry "valid" bit ** Do NOT clear the rest - save it for debugging. ** We should only clear bits that have previously ** been enabled. */ ioc->pdir_base[off] &= ~(0x80000000000000FFULL);#else /* ** If we want to maintain the PDIR as valid, put in ** the spill page so devices prefetching won't ** cause a hard fail. */ ioc->pdir_base[off] = (0x80000000000000FFULL | (u64)prefetch_spill_page);#endif } else { u32 t = get_order(byte_cnt) + PAGE_SHIFT; iovp |= t; ASSERT(t <= 31); /* 2GB! Max value of "size" field */ do { /* verify this pdir entry is enabled */ ASSERT(ioc->pdir_base[off] >> 63);#ifndef FULL_VALID_PDIR /* clear I/O Pdir entry "valid" bit first */ ioc->pdir_base[off] &= ~(0x80000000000000FFULL);#else ioc->pdir_base[off] = (0x80000000000000FFULL | (u64)prefetch_spill_page);#endif off++; byte_cnt -= IOVP_SIZE; } while (byte_cnt > 0); } WRITE_REG(iovp | ioc->ibase, ioc->ioc_hpa+IOC_PCOM);}/** * sba_map_single - map one buffer and return IOVA for DMA * @dev: instance of PCI owned by the driver that's asking. * @addr: driver buffer to map. * @size: number of bytes to map in driver buffer. * @direction: R/W or both. * * See Documentation/DMA-mapping.txt */dma_addr_tsba_map_single(struct pci_dev *dev, void *addr, size_t size, int direction){ struct ioc *ioc; unsigned long flags; dma_addr_t iovp; dma_addr_t offset; u64 *pdir_start; int pide;#ifdef ALLOW_IOV_BYPASS unsigned long pci_addr = virt_to_phys(addr);#endif ioc = GET_IOC(dev); ASSERT(ioc);#ifdef ALLOW_IOV_BYPASS /* ** Check if the PCI device can DMA to ptr... if so, just return ptr */ if ((pci_addr & ~dev->dma_mask) == 0) { /* ** Device is bit capable of DMA'ing to the buffer... ** just return the PCI address of ptr */#ifdef CONFIG_PROC_FS spin_lock_irqsave(&ioc->res_lock, flags); ioc->msingle_bypass++; spin_unlock_irqrestore(&ioc->res_lock, flags);#endif DBG_BYPASS("sba_map_single() bypass mask/addr: 0x%lx/0x%lx\n", dev->dma_mask, pci_addr); return pci_addr; }#endif ASSERT(size > 0); ASSERT(size <= DMA_CHUNK_SIZE); /* save offset bits */ offset = ((dma_addr_t) (long) addr) & ~IOVP_MASK; /* round up to nearest IOVP_SIZE */ size = (size + offset + ~IOVP_MASK) & IOVP_MASK; spin_lock_irqsave(&ioc->res_lock, flags);#ifdef ASSERT_PDIR_SANITY if (sba_check_pdir(ioc,"Check before sba_map_single()")) panic("Sanity check failed");#endif#ifdef CONFIG_PROC_FS ioc->msingle_calls++; ioc->msingle_pages += size >> IOVP_SHIFT;#endif pide = sba_alloc_range(ioc, size); iovp = (dma_addr_t) pide << IOVP_SHIFT; DBG_RUN("%s() 0x%p -> 0x%lx\n", __FUNCTION__, addr, (long) iovp | offset); pdir_start = &(ioc->pdir_base[pide]); while (size > 0) { ASSERT(((u8 *)pdir_start)[7] == 0); /* verify availability */ sba_io_pdir_entry(pdir_start, (unsigned long) addr); DBG_RUN(" pdir 0x%p %lx\n", pdir_start, *pdir_start); addr += IOVP_SIZE; size -= IOVP_SIZE; pdir_start++; } /* form complete address */#ifdef ASSERT_PDIR_SANITY sba_check_pdir(ioc,"Check after sba_map_single()");#endif spin_unlock_irqrestore(&ioc->res_lock, flags); return SBA_IOVA(ioc, iovp, offset, DEFAULT_DMA_HINT_REG);}/** * sba_unmap_single - unmap one IOVA and free resources * @dev: instance of PCI owned by the driver that's asking. * @iova: IOVA of driver buffer previously mapped. * @size: number of bytes mapped in driver buffer. * @direction: R/W or both. * * See Documentation/DMA-mapping.txt */void sba_unmap_single(struct pci_dev *dev, dma_addr_t iova, size_t size, int direction){ struct ioc *ioc;#if DELAYED_RESOURCE_CNT > 0 struct sba_dma_pair *d;#endif unsigned long flags; dma_addr_t offset; ioc = GET_IOC(dev); ASSERT(ioc);#ifdef ALLOW_IOV_BYPASS if ((iova & ioc->imask) != ioc->ibase) { /* ** Address does not fall w/in IOVA, must be bypassing */#ifdef CONFIG_PROC_FS spin_lock_irqsave(&ioc->res_lock, flags); ioc->usingle_bypass++; spin_unlock_irqrestore(&ioc->res_lock, flags);#endif DBG_BYPASS("sba_unmap_single() bypass addr: 0x%lx\n", iova);#ifdef ENABLE_MARK_CLEAN if (direction == PCI_DMA_FROMDEVICE) { mark_clean(phys_to_virt(iova), size); }#endif return; }#endif offset = iova & ~IOVP_MASK; DBG_RUN("%s() iovp 0x%lx/%x\n", __FUNCTION__, (long) iova, size); iova ^= offset; /* clear offset bits */ size += offset; size = ROUNDUP(size, IOVP_SIZE); spin_lock_irqsave(&ioc->res_lock, flags);#ifdef CONFIG_PROC_FS ioc->usingle_calls++; ioc->usingle_pages += size >> IOVP_SHIFT;#endif#if DELAYED_RESOURCE_CNT > 0 d = &(ioc->saved[ioc->saved_cnt]); d->iova = iova; d->size = size; if (++(ioc->saved_cnt) >= DELAYED_RESOURCE_CNT) { int cnt = ioc->saved_cnt; while (cnt--) { sba_mark_invalid(ioc, d->iova, d->size); sba_free_range(ioc, d->iova, d->size); d--; } ioc->saved_cnt = 0; READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */ }#else /* DELAYED_RESOURCE_CNT == 0 */ sba_mark_invalid(ioc, iova, size); sba_free_range(ioc, iova, size); READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */#endif /* DELAYED_RESOURCE_CNT == 0 */#ifdef ENABLE_MARK_CLEAN if (direction == PCI_DMA_FROMDEVICE) { u32 iovp = (u32) SBA_IOVP(ioc,iova); int off = PDIR_INDEX(iovp); void *addr; if (size <= IOVP_SIZE) { addr = phys_to_virt(ioc->pdir_base[off] & ~0xE000000000000FFFULL); mark_clean(addr, size); } else { size_t byte_cnt = size; do { addr = phys_to_virt(ioc->pdir_base[off] & ~0xE000000000000FFFULL); mark_clean(addr, min(byte_cnt, IOVP_SIZE)); off++; byte_cnt -= IOVP_SIZE; } while (byte_cnt > 0); } }#endif spin_unlock_irqrestore(&ioc->res_lock, flags); /* XXX REVISIT for 2.5 Linux - need syncdma for zero-copy support. ** For Astro based systems this isn't a big deal WRT performance. ** As long as 2.4 kernels copyin/copyout data from/to userspace, ** we don't need the syncdma. The issue here is I/O MMU cachelines ** are *not* coherent in all cases. May be hwrev dependent. ** Need to investigate more. asm volatile("syncdma"); */}/** * sba_alloc_consistent - allocate/map shared mem for DMA * @hwdev: instance of PCI owned by the driver that's asking. * @size: number of bytes mapped in driver buffer. * @dma_handle: IOVA of new buffer. * * See Documentation/DMA-mapping.txt */void *sba_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle){ struct ioc *ioc; void *ret; if (!hwdev) { /* only support PCI */ *dma_handle = 0; return 0; } ret = (void *) __get_free_pages(GFP_ATOMIC, get_order(size)); if (ret) { memset(ret, 0, size); /* * REVISIT: if sba_map_single starts needing more * than dma_mask from the device, this needs to be * updated. */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -