Skip to content

Commit

Permalink
iommu/dmar: Reserve mmio space used by the IOMMU, if the BIOS forgets to
Browse files Browse the repository at this point in the history
Intel-iommu initialization doesn't currently reserve the memory
used for the IOMMU registers. This can allow the pci resource
allocator to assign a device BAR to the same address as the
IOMMU registers. This can cause some not so nice side affects
when the driver ioremap's that region.

Introduced two helper functions to map & unmap the IOMMU
registers as well as simplify the init and exit paths.

Signed-off-by: Donald Dutile <ddutile@redhat.com>
Acked-by: Chris Wright <chrisw@redhat.com>
Cc: iommu@lists.linux-foundation.org
Cc: suresh.b.siddha@intel.com
Cc: dwmw2@infradead.org
Link: http://lkml.kernel.org/r/1338845342-12464-3-git-send-email-ddutile@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
  • Loading branch information
Donald Dutile authored and Ingo Molnar committed Jun 8, 2012
1 parent bf947fc commit 6f5cf52
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 27 deletions.
111 changes: 84 additions & 27 deletions drivers/iommu/dmar.c
Original file line number Diff line number Diff line change
Expand Up @@ -570,14 +570,89 @@ int __init detect_intel_iommu(void)
}


static void unmap_iommu(struct intel_iommu *iommu)
{
iounmap(iommu->reg);
release_mem_region(iommu->reg_phys, iommu->reg_size);
}

/**
* map_iommu: map the iommu's registers
* @iommu: the iommu to map
* @phys_addr: the physical address of the base resgister
*
* Memory map the iommu's registers. Start w/ a single page, and
* possibly expand if that turns out to be insufficent.
*/
static int map_iommu(struct intel_iommu *iommu, u64 phys_addr)
{
int map_size, err=0;

iommu->reg_phys = phys_addr;
iommu->reg_size = VTD_PAGE_SIZE;

if (!request_mem_region(iommu->reg_phys, iommu->reg_size, iommu->name)) {
pr_err("IOMMU: can't reserve memory\n");
err = -EBUSY;
goto out;
}

iommu->reg = ioremap(iommu->reg_phys, iommu->reg_size);
if (!iommu->reg) {
pr_err("IOMMU: can't map the region\n");
err = -ENOMEM;
goto release;
}

iommu->cap = dmar_readq(iommu->reg + DMAR_CAP_REG);
iommu->ecap = dmar_readq(iommu->reg + DMAR_ECAP_REG);

if (iommu->cap == (uint64_t)-1 && iommu->ecap == (uint64_t)-1) {
err = -EINVAL;
warn_invalid_dmar(phys_addr, " returns all ones");
goto unmap;
}

/* the registers might be more than one page */
map_size = max_t(int, ecap_max_iotlb_offset(iommu->ecap),
cap_max_fault_reg_offset(iommu->cap));
map_size = VTD_PAGE_ALIGN(map_size);
if (map_size > iommu->reg_size) {
iounmap(iommu->reg);
release_mem_region(iommu->reg_phys, iommu->reg_size);
iommu->reg_size = map_size;
if (!request_mem_region(iommu->reg_phys, iommu->reg_size,
iommu->name)) {
pr_err("IOMMU: can't reserve memory\n");
err = -EBUSY;
goto out;
}
iommu->reg = ioremap(iommu->reg_phys, iommu->reg_size);
if (!iommu->reg) {
pr_err("IOMMU: can't map the region\n");
err = -ENOMEM;
goto release;
}
}
err = 0;
goto out;

unmap:
iounmap(iommu->reg);
release:
release_mem_region(iommu->reg_phys, iommu->reg_size);
out:
return err;
}

int alloc_iommu(struct dmar_drhd_unit *drhd)
{
struct intel_iommu *iommu;
int map_size;
u32 ver;
static int iommu_allocated = 0;
int agaw = 0;
int msagaw = 0;
int err;

if (!drhd->reg_base_addr) {
warn_invalid_dmar(0, "");
Expand All @@ -591,19 +666,13 @@ int alloc_iommu(struct dmar_drhd_unit *drhd)
iommu->seq_id = iommu_allocated++;
sprintf (iommu->name, "dmar%d", iommu->seq_id);

iommu->reg = ioremap(drhd->reg_base_addr, VTD_PAGE_SIZE);
if (!iommu->reg) {
pr_err("IOMMU: can't map the region\n");
err = map_iommu(iommu, drhd->reg_base_addr);
if (err) {
pr_err("IOMMU: failed to map %s\n", iommu->name);
goto error;
}
iommu->cap = dmar_readq(iommu->reg + DMAR_CAP_REG);
iommu->ecap = dmar_readq(iommu->reg + DMAR_ECAP_REG);

if (iommu->cap == (uint64_t)-1 && iommu->ecap == (uint64_t)-1) {
warn_invalid_dmar(drhd->reg_base_addr, " returns all ones");
goto err_unmap;
}

err = -EINVAL;
agaw = iommu_calculate_agaw(iommu);
if (agaw < 0) {
pr_err("Cannot get a valid agaw for iommu (seq_id = %d)\n",
Expand All @@ -621,19 +690,6 @@ int alloc_iommu(struct dmar_drhd_unit *drhd)

iommu->node = -1;

/* the registers might be more than one page */
map_size = max_t(int, ecap_max_iotlb_offset(iommu->ecap),
cap_max_fault_reg_offset(iommu->cap));
map_size = VTD_PAGE_ALIGN(map_size);
if (map_size > VTD_PAGE_SIZE) {
iounmap(iommu->reg);
iommu->reg = ioremap(drhd->reg_base_addr, map_size);
if (!iommu->reg) {
pr_err("IOMMU: can't map the region\n");
goto error;
}
}

ver = readl(iommu->reg + DMAR_VER_REG);
pr_info("IOMMU %d: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
iommu->seq_id,
Expand All @@ -648,10 +704,10 @@ int alloc_iommu(struct dmar_drhd_unit *drhd)
return 0;

err_unmap:
iounmap(iommu->reg);
unmap_iommu(iommu);
error:
kfree(iommu);
return -1;
return err;
}

void free_iommu(struct intel_iommu *iommu)
Expand All @@ -662,7 +718,8 @@ void free_iommu(struct intel_iommu *iommu)
free_dmar_iommu(iommu);

if (iommu->reg)
iounmap(iommu->reg);
unmap_iommu(iommu);

kfree(iommu);
}

Expand Down
2 changes: 2 additions & 0 deletions include/linux/intel-iommu.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ enum {

struct intel_iommu {
void __iomem *reg; /* Pointer to hardware regs, virtual addr */
u64 reg_phys; /* physical address of hw register set */
u64 reg_size; /* size of hw register set */
u64 cap;
u64 ecap;
u32 gcmd; /* Holds TE, EAFL. Don't need SRTP, SFL, WBF */
Expand Down

0 comments on commit 6f5cf52

Please sign in to comment.