summaryrefslogtreecommitdiffstats
path: root/sound/pci/emu10k1/emu10k1_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/emu10k1/emu10k1_main.c')
-rw-r--r--sound/pci/emu10k1/emu10k1_main.c49
1 files changed, 44 insertions, 5 deletions
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
index 8decd2a7a404..18267de3a269 100644
--- a/sound/pci/emu10k1/emu10k1_main.c
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -36,6 +36,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/iommu.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
@@ -1758,6 +1759,38 @@ static struct snd_emu_chip_details emu_chip_details[] = {
{ } /* terminator */
};
+/*
+ * The chip (at least the Audigy 2 CA0102 chip, but most likely others, too)
+ * has a problem that from time to time it likes to do few DMA reads a bit
+ * beyond its normal allocation and gets very confused if these reads get
+ * blocked by a IOMMU.
+ *
+ * This behaviour has been observed for the first (reserved) page
+ * (for which it happens multiple times at every playback), often for various
+ * synth pages and sometimes for PCM playback buffers and the page table
+ * memory itself.
+ *
+ * As a workaround let's widen these DMA allocations by an extra page if we
+ * detect that the device is behind a non-passthrough IOMMU.
+ */
+static void snd_emu10k1_detect_iommu(struct snd_emu10k1 *emu)
+{
+ struct iommu_domain *domain;
+
+ emu->iommu_workaround = false;
+
+ if (!iommu_present(emu->card->dev->bus))
+ return;
+
+ domain = iommu_get_domain_for_dev(emu->card->dev);
+ if (domain && domain->type == IOMMU_DOMAIN_IDENTITY)
+ return;
+
+ dev_notice(emu->card->dev,
+ "non-passthrough IOMMU detected, widening DMA allocations");
+ emu->iommu_workaround = true;
+}
+
int snd_emu10k1_create(struct snd_card *card,
struct pci_dev *pci,
unsigned short extin_mask,
@@ -1770,6 +1803,7 @@ int snd_emu10k1_create(struct snd_card *card,
struct snd_emu10k1 *emu;
int idx, err;
int is_audigy;
+ size_t page_table_size;
unsigned int silent_page;
const struct snd_emu_chip_details *c;
static struct snd_device_ops ops = {
@@ -1867,6 +1901,8 @@ int snd_emu10k1_create(struct snd_card *card,
is_audigy = emu->audigy = c->emu10k2_chip;
+ snd_emu10k1_detect_iommu(emu);
+
/* set addressing mode */
emu->address_mode = is_audigy ? 0 : 1;
/* set the DMA transfer mask */
@@ -1893,8 +1929,11 @@ int snd_emu10k1_create(struct snd_card *card,
emu->port = pci_resource_start(pci, 0);
emu->max_cache_pages = max_cache_bytes >> PAGE_SHIFT;
- if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
- (emu->address_mode ? 32 : 16) * 1024, &emu->ptb_pages) < 0) {
+
+ page_table_size = sizeof(u32) * (emu->address_mode ? MAXPAGES1 :
+ MAXPAGES0);
+ if (snd_emu10k1_alloc_pages_maybe_wider(emu, page_table_size,
+ &emu->ptb_pages) < 0) {
err = -ENOMEM;
goto error;
}
@@ -1910,8 +1949,8 @@ int snd_emu10k1_create(struct snd_card *card,
goto error;
}
- if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
- EMUPAGESIZE, &emu->silent_page) < 0) {
+ if (snd_emu10k1_alloc_pages_maybe_wider(emu, EMUPAGESIZE,
+ &emu->silent_page) < 0) {
err = -ENOMEM;
goto error;
}
@@ -1995,7 +2034,7 @@ int snd_emu10k1_create(struct snd_card *card,
0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT;
/* Clear silent pages and set up pointers */
- memset(emu->silent_page.area, 0, PAGE_SIZE);
+ memset(emu->silent_page.area, 0, emu->silent_page.bytes);
silent_page = emu->silent_page.addr << emu->address_mode;
for (idx = 0; idx < (emu->address_mode ? MAXPAGES1 : MAXPAGES0); idx++)
((u32 *)emu->ptb_pages.area)[idx] = cpu_to_le32(silent_page | idx);