Commit 5f6602a1 authored by Alex Williamson's avatar Alex Williamson Committed by Tony Luck

[IA64] sba_iommu bug fixes

   This fixes a couple of bugs in the zx1/sx1000 sba_iommu.  These are
all pretty low likelihood of hitting.  The first problem is a simple off
by one, deep in the sba_alloc_range() error path.  Surrounding that was
a lock ordering problem that could have potentially deadlocked with the
order the locks are grabbed in sba_unmap_single().  I moved the resource
locking into sba_search_bitmap() to prevent this.  Finally, there's a
potential race between unmapping pdir entries and marking incoming DMA
pages clean.  If you see any oddities, please let me know, but I've
tested it pretty thoroughly here.  Tony, please apply.  Thanks,

BTW, many of the options in this driver not on by default are becoming
more and more broken.  I'll be working on some patches to clean them
out, but I wanted to get this bug fix out first.
Signed-off-by: default avatarAlex Williamson <alex.williamson@hp.com>
Signed-off-by: default avatarTony Luck <tony.luck@intel.com>
parent fde740e4
/* /*
** IA64 System Bus Adapter (SBA) I/O MMU manager ** IA64 System Bus Adapter (SBA) I/O MMU manager
** **
** (c) Copyright 2002-2004 Alex Williamson ** (c) Copyright 2002-2005 Alex Williamson
** (c) Copyright 2002-2003 Grant Grundler ** (c) Copyright 2002-2003 Grant Grundler
** (c) Copyright 2002-2004 Hewlett-Packard Company ** (c) Copyright 2002-2005 Hewlett-Packard Company
** **
** Portions (c) 2000 Grant Grundler (from parisc I/O MMU code) ** Portions (c) 2000 Grant Grundler (from parisc I/O MMU code)
** Portions (c) 1999 Dave S. Miller (from sparc64 I/O MMU code) ** Portions (c) 1999 Dave S. Miller (from sparc64 I/O MMU code)
...@@ -459,21 +459,32 @@ get_iovp_order (unsigned long size) ...@@ -459,21 +459,32 @@ get_iovp_order (unsigned long size)
* sba_search_bitmap - find free space in IO PDIR resource bitmap * sba_search_bitmap - find free space in IO PDIR resource bitmap
* @ioc: IO MMU structure which owns the pdir we are interested in. * @ioc: IO MMU structure which owns the pdir we are interested in.
* @bits_wanted: number of entries we need. * @bits_wanted: number of entries we need.
* @use_hint: use res_hint to indicate where to start looking
* *
* Find consecutive free bits in resource bitmap. * Find consecutive free bits in resource bitmap.
* Each bit represents one entry in the IO Pdir. * Each bit represents one entry in the IO Pdir.
* Cool perf optimization: search for log2(size) bits at a time. * Cool perf optimization: search for log2(size) bits at a time.
*/ */
static SBA_INLINE unsigned long static SBA_INLINE unsigned long
sba_search_bitmap(struct ioc *ioc, unsigned long bits_wanted) sba_search_bitmap(struct ioc *ioc, unsigned long bits_wanted, int use_hint)
{ {
unsigned long *res_ptr = ioc->res_hint; unsigned long *res_ptr;
unsigned long *res_end = (unsigned long *) &(ioc->res_map[ioc->res_size]); unsigned long *res_end = (unsigned long *) &(ioc->res_map[ioc->res_size]);
unsigned long pide = ~0UL; unsigned long flags, pide = ~0UL;
ASSERT(((unsigned long) ioc->res_hint & (sizeof(unsigned long) - 1UL)) == 0); ASSERT(((unsigned long) ioc->res_hint & (sizeof(unsigned long) - 1UL)) == 0);
ASSERT(res_ptr < res_end); ASSERT(res_ptr < res_end);
spin_lock_irqsave(&ioc->res_lock, flags);
/* Allow caller to force a search through the entire resource space */
if (likely(use_hint)) {
res_ptr = ioc->res_hint;
} else {
res_ptr = (ulong *)ioc->res_map;
ioc->res_bitshift = 0;
}
/* /*
* N.B. REO/Grande defect AR2305 can cause TLB fetch timeouts * N.B. REO/Grande defect AR2305 can cause TLB fetch timeouts
* if a TLB entry is purged while in use. sba_mark_invalid() * if a TLB entry is purged while in use. sba_mark_invalid()
...@@ -570,10 +581,12 @@ not_found: ...@@ -570,10 +581,12 @@ not_found:
prefetch(ioc->res_map); prefetch(ioc->res_map);
ioc->res_hint = (unsigned long *) ioc->res_map; ioc->res_hint = (unsigned long *) ioc->res_map;
ioc->res_bitshift = 0; ioc->res_bitshift = 0;
spin_unlock_irqrestore(&ioc->res_lock, flags);
return (pide); return (pide);
found_it: found_it:
ioc->res_hint = res_ptr; ioc->res_hint = res_ptr;
spin_unlock_irqrestore(&ioc->res_lock, flags);
return (pide); return (pide);
} }
...@@ -594,36 +607,36 @@ sba_alloc_range(struct ioc *ioc, size_t size) ...@@ -594,36 +607,36 @@ sba_alloc_range(struct ioc *ioc, size_t size)
unsigned long itc_start; unsigned long itc_start;
#endif #endif
unsigned long pide; unsigned long pide;
unsigned long flags;
ASSERT(pages_needed); ASSERT(pages_needed);
ASSERT(0 == (size & ~iovp_mask)); ASSERT(0 == (size & ~iovp_mask));
spin_lock_irqsave(&ioc->res_lock, flags);
#ifdef PDIR_SEARCH_TIMING #ifdef PDIR_SEARCH_TIMING
itc_start = ia64_get_itc(); itc_start = ia64_get_itc();
#endif #endif
/* /*
** "seek and ye shall find"...praying never hurts either... ** "seek and ye shall find"...praying never hurts either...
*/ */
pide = sba_search_bitmap(ioc, pages_needed); pide = sba_search_bitmap(ioc, pages_needed, 1);
if (unlikely(pide >= (ioc->res_size << 3))) { if (unlikely(pide >= (ioc->res_size << 3))) {
pide = sba_search_bitmap(ioc, pages_needed); pide = sba_search_bitmap(ioc, pages_needed, 0);
if (unlikely(pide >= (ioc->res_size << 3))) { if (unlikely(pide >= (ioc->res_size << 3))) {
#if DELAYED_RESOURCE_CNT > 0 #if DELAYED_RESOURCE_CNT > 0
unsigned long flags;
/* /*
** With delayed resource freeing, we can give this one more shot. We're ** With delayed resource freeing, we can give this one more shot. We're
** getting close to being in trouble here, so do what we can to make this ** getting close to being in trouble here, so do what we can to make this
** one count. ** one count.
*/ */
spin_lock(&ioc->saved_lock); spin_lock_irqsave(&ioc->saved_lock, flags);
if (ioc->saved_cnt > 0) { if (ioc->saved_cnt > 0) {
struct sba_dma_pair *d; struct sba_dma_pair *d;
int cnt = ioc->saved_cnt; int cnt = ioc->saved_cnt;
d = &(ioc->saved[ioc->saved_cnt]); d = &(ioc->saved[ioc->saved_cnt - 1]);
spin_lock(&ioc->res_lock);
while (cnt--) { while (cnt--) {
sba_mark_invalid(ioc, d->iova, d->size); sba_mark_invalid(ioc, d->iova, d->size);
sba_free_range(ioc, d->iova, d->size); sba_free_range(ioc, d->iova, d->size);
...@@ -631,10 +644,11 @@ sba_alloc_range(struct ioc *ioc, size_t size) ...@@ -631,10 +644,11 @@ sba_alloc_range(struct ioc *ioc, size_t size)
} }
ioc->saved_cnt = 0; ioc->saved_cnt = 0;
READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */ READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */
spin_unlock(&ioc->res_lock);
} }
spin_unlock(&ioc->saved_lock); spin_unlock_irqrestore(&ioc->saved_lock, flags);
pide = sba_search_bitmap(ioc, pages_needed); pide = sba_search_bitmap(ioc, pages_needed, 0);
if (unlikely(pide >= (ioc->res_size << 3))) if (unlikely(pide >= (ioc->res_size << 3)))
panic(__FILE__ ": I/O MMU @ %p is out of mapping resources\n", panic(__FILE__ ": I/O MMU @ %p is out of mapping resources\n",
ioc->ioc_hpa); ioc->ioc_hpa);
...@@ -664,8 +678,6 @@ sba_alloc_range(struct ioc *ioc, size_t size) ...@@ -664,8 +678,6 @@ sba_alloc_range(struct ioc *ioc, size_t size)
(uint) ((unsigned long) ioc->res_hint - (unsigned long) ioc->res_map), (uint) ((unsigned long) ioc->res_hint - (unsigned long) ioc->res_map),
ioc->res_bitshift ); ioc->res_bitshift );
spin_unlock_irqrestore(&ioc->res_lock, flags);
return (pide); return (pide);
} }
...@@ -950,6 +962,30 @@ sba_map_single(struct device *dev, void *addr, size_t size, int dir) ...@@ -950,6 +962,30 @@ sba_map_single(struct device *dev, void *addr, size_t size, int dir)
return SBA_IOVA(ioc, iovp, offset); return SBA_IOVA(ioc, iovp, offset);
} }
#ifdef ENABLE_MARK_CLEAN
static SBA_INLINE void
sba_mark_clean(struct ioc *ioc, dma_addr_t iova, size_t size)
{
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 {
do {
addr = phys_to_virt(ioc->pdir_base[off] &
~0xE000000000000FFFULL);
mark_clean(addr, min(size, iovp_size));
off++;
size -= iovp_size;
} while (size > 0);
}
}
#endif
/** /**
* sba_unmap_single - unmap one IOVA and free resources * sba_unmap_single - unmap one IOVA and free resources
* @dev: instance of PCI owned by the driver that's asking. * @dev: instance of PCI owned by the driver that's asking.
...@@ -995,6 +1031,10 @@ void sba_unmap_single(struct device *dev, dma_addr_t iova, size_t size, int dir) ...@@ -995,6 +1031,10 @@ void sba_unmap_single(struct device *dev, dma_addr_t iova, size_t size, int dir)
size += offset; size += offset;
size = ROUNDUP(size, iovp_size); size = ROUNDUP(size, iovp_size);
#ifdef ENABLE_MARK_CLEAN
if (dir == DMA_FROM_DEVICE)
sba_mark_clean(ioc, iova, size);
#endif
#if DELAYED_RESOURCE_CNT > 0 #if DELAYED_RESOURCE_CNT > 0
spin_lock_irqsave(&ioc->saved_lock, flags); spin_lock_irqsave(&ioc->saved_lock, flags);
...@@ -1021,30 +1061,6 @@ void sba_unmap_single(struct device *dev, dma_addr_t iova, size_t size, int dir) ...@@ -1021,30 +1061,6 @@ void sba_unmap_single(struct device *dev, dma_addr_t iova, size_t size, int dir)
READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */ READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */
spin_unlock_irqrestore(&ioc->res_lock, flags); spin_unlock_irqrestore(&ioc->res_lock, flags);
#endif /* DELAYED_RESOURCE_CNT == 0 */ #endif /* DELAYED_RESOURCE_CNT == 0 */
#ifdef ENABLE_MARK_CLEAN
if (dir == DMA_FROM_DEVICE) {
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
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment