Skip to content

Commit

Permalink
Merge branch 'core' into x86/vt-d
Browse files Browse the repository at this point in the history
Conflicts:
	drivers/iommu/intel-iommu.c
  • Loading branch information
Joerg Roedel committed Jul 23, 2014
2 parents aa4d066 + e09f8ea commit cbb24a2
Show file tree
Hide file tree
Showing 25 changed files with 786 additions and 478 deletions.
17 changes: 17 additions & 0 deletions Documentation/ABI/testing/sysfs-class-iommu
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
What: /sys/class/iommu/<iommu>/devices/
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
IOMMU drivers are able to link devices managed by a
given IOMMU here to allow association of IOMMU to
device.

What: /sys/devices/.../iommu
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
IOMMU drivers are able to link the IOMMU for a
given device here to allow association of device to
IOMMU.
14 changes: 14 additions & 0 deletions Documentation/ABI/testing/sysfs-class-iommu-amd-iommu
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
What: /sys/class/iommu/<iommu>/amd-iommu/cap
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
IOMMU capability header as documented in the AMD IOMMU
specification. Format: %x

What: /sys/class/iommu/<iommu>/amd-iommu/features
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
Extended features of the IOMMU. Format: %llx
32 changes: 32 additions & 0 deletions Documentation/ABI/testing/sysfs-class-iommu-intel-iommu
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
What: /sys/class/iommu/<iommu>/intel-iommu/address
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
Physical address of the VT-d DRHD for this IOMMU.
Format: %llx. This allows association of a sysfs
intel-iommu with a DMAR DRHD table entry.

What: /sys/class/iommu/<iommu>/intel-iommu/cap
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
The cached hardware capability register value
of this DRHD unit. Format: %llx.

What: /sys/class/iommu/<iommu>/intel-iommu/ecap
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
The cached hardware extended capability register
value of this DRHD unit. Format: %llx.

What: /sys/class/iommu/<iommu>/intel-iommu/version
Date: June 2014
KernelVersion: 3.17
Contact: Alex Williamson <alex.williamson@redhat.com>
Description:
The architecture version as reported from the
VT-d VER_REG. Format: %d:%d, major:minor
1 change: 1 addition & 0 deletions drivers/iommu/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
obj-$(CONFIG_IOMMU_API) += iommu.o
obj-$(CONFIG_IOMMU_API) += iommu-traces.o
obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o
obj-$(CONFIG_OF_IOMMU) += of_iommu.o
obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
Expand Down
224 changes: 73 additions & 151 deletions drivers/iommu/amd_iommu.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
#include "amd_iommu_proto.h"
#include "amd_iommu_types.h"
#include "irq_remapping.h"
#include "pci.h"

#define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28))

Expand Down Expand Up @@ -81,7 +80,7 @@ LIST_HEAD(hpet_map);
*/
static struct protection_domain *pt_domain;

static struct iommu_ops amd_iommu_ops;
static const struct iommu_ops amd_iommu_ops;

static ATOMIC_NOTIFIER_HEAD(ppr_notifier);
int amd_iommu_max_glx_val = -1;
Expand Down Expand Up @@ -133,9 +132,6 @@ static void free_dev_data(struct iommu_dev_data *dev_data)
list_del(&dev_data->dev_data_list);
spin_unlock_irqrestore(&dev_data_list_lock, flags);

if (dev_data->group)
iommu_group_put(dev_data->group);

kfree(dev_data);
}

Expand Down Expand Up @@ -264,167 +260,79 @@ static bool check_device(struct device *dev)
return true;
}

static struct pci_bus *find_hosted_bus(struct pci_bus *bus)
{
while (!bus->self) {
if (!pci_is_root_bus(bus))
bus = bus->parent;
else
return ERR_PTR(-ENODEV);
}

return bus;
}

#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)

static struct pci_dev *get_isolation_root(struct pci_dev *pdev)
{
struct pci_dev *dma_pdev = pdev;

/* Account for quirked devices */
swap_pci_ref(&dma_pdev, pci_get_dma_source(dma_pdev));

/*
* If it's a multifunction device that does not support our
* required ACS flags, add to the same group as lowest numbered
* function that also does not suport the required ACS flags.
*/
if (dma_pdev->multifunction &&
!pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)) {
u8 i, slot = PCI_SLOT(dma_pdev->devfn);

for (i = 0; i < 8; i++) {
struct pci_dev *tmp;

tmp = pci_get_slot(dma_pdev->bus, PCI_DEVFN(slot, i));
if (!tmp)
continue;

if (!pci_acs_enabled(tmp, REQ_ACS_FLAGS)) {
swap_pci_ref(&dma_pdev, tmp);
break;
}
pci_dev_put(tmp);
}
}

/*
* Devices on the root bus go through the iommu. If that's not us,
* find the next upstream device and test ACS up to the root bus.
* Finding the next device may require skipping virtual buses.
*/
while (!pci_is_root_bus(dma_pdev->bus)) {
struct pci_bus *bus = find_hosted_bus(dma_pdev->bus);
if (IS_ERR(bus))
break;

if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS))
break;

swap_pci_ref(&dma_pdev, pci_dev_get(bus->self));
}

return dma_pdev;
}

static int use_pdev_iommu_group(struct pci_dev *pdev, struct device *dev)
static int init_iommu_group(struct device *dev)
{
struct iommu_group *group = iommu_group_get(&pdev->dev);
int ret;
struct iommu_group *group;

if (!group) {
group = iommu_group_alloc();
if (IS_ERR(group))
return PTR_ERR(group);
group = iommu_group_get_for_dev(dev);

WARN_ON(&pdev->dev != dev);
}
if (IS_ERR(group))
return PTR_ERR(group);

ret = iommu_group_add_device(group, dev);
iommu_group_put(group);
return ret;
return 0;
}

static int use_dev_data_iommu_group(struct iommu_dev_data *dev_data,
struct device *dev)
static int __last_alias(struct pci_dev *pdev, u16 alias, void *data)
{
if (!dev_data->group) {
struct iommu_group *group = iommu_group_alloc();
if (IS_ERR(group))
return PTR_ERR(group);

dev_data->group = group;
}

return iommu_group_add_device(dev_data->group, dev);
*(u16 *)data = alias;
return 0;
}

static int init_iommu_group(struct device *dev)
static u16 get_alias(struct device *dev)
{
struct iommu_dev_data *dev_data;
struct iommu_group *group;
struct pci_dev *dma_pdev;
int ret;

group = iommu_group_get(dev);
if (group) {
iommu_group_put(group);
return 0;
}

dev_data = find_dev_data(get_device_id(dev));
if (!dev_data)
return -ENOMEM;
struct pci_dev *pdev = to_pci_dev(dev);
u16 devid, ivrs_alias, pci_alias;

if (dev_data->alias_data) {
u16 alias;
struct pci_bus *bus;
devid = get_device_id(dev);
ivrs_alias = amd_iommu_alias_table[devid];
pci_for_each_dma_alias(pdev, __last_alias, &pci_alias);

if (dev_data->alias_data->group)
goto use_group;
if (ivrs_alias == pci_alias)
return ivrs_alias;

/*
* If the alias device exists, it's effectively just a first
* level quirk for finding the DMA source.
*/
alias = amd_iommu_alias_table[dev_data->devid];
dma_pdev = pci_get_bus_and_slot(alias >> 8, alias & 0xff);
if (dma_pdev) {
dma_pdev = get_isolation_root(dma_pdev);
goto use_pdev;
/*
* DMA alias showdown
*
* The IVRS is fairly reliable in telling us about aliases, but it
* can't know about every screwy device. If we don't have an IVRS
* reported alias, use the PCI reported alias. In that case we may
* still need to initialize the rlookup and dev_table entries if the
* alias is to a non-existent device.
*/
if (ivrs_alias == devid) {
if (!amd_iommu_rlookup_table[pci_alias]) {
amd_iommu_rlookup_table[pci_alias] =
amd_iommu_rlookup_table[devid];
memcpy(amd_iommu_dev_table[pci_alias].data,
amd_iommu_dev_table[devid].data,
sizeof(amd_iommu_dev_table[pci_alias].data));
}

/*
* If the alias is virtual, try to find a parent device
* and test whether the IOMMU group is actualy rooted above
* the alias. Be careful to also test the parent device if
* we think the alias is the root of the group.
*/
bus = pci_find_bus(0, alias >> 8);
if (!bus)
goto use_group;

bus = find_hosted_bus(bus);
if (IS_ERR(bus) || !bus->self)
goto use_group;
return pci_alias;
}

dma_pdev = get_isolation_root(pci_dev_get(bus->self));
if (dma_pdev != bus->self || (dma_pdev->multifunction &&
!pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)))
goto use_pdev;
pr_info("AMD-Vi: Using IVRS reported alias %02x:%02x.%d "
"for device %s[%04x:%04x], kernel reported alias "
"%02x:%02x.%d\n", PCI_BUS_NUM(ivrs_alias), PCI_SLOT(ivrs_alias),
PCI_FUNC(ivrs_alias), dev_name(dev), pdev->vendor, pdev->device,
PCI_BUS_NUM(pci_alias), PCI_SLOT(pci_alias),
PCI_FUNC(pci_alias));

pci_dev_put(dma_pdev);
goto use_group;
/*
* If we don't have a PCI DMA alias and the IVRS alias is on the same
* bus, then the IVRS table may know about a quirk that we don't.
*/
if (pci_alias == devid &&
PCI_BUS_NUM(ivrs_alias) == pdev->bus->number) {
pdev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN;
pdev->dma_alias_devfn = ivrs_alias & 0xff;
pr_info("AMD-Vi: Added PCI DMA alias %02x.%d for %s\n",
PCI_SLOT(ivrs_alias), PCI_FUNC(ivrs_alias),
dev_name(dev));
}

dma_pdev = get_isolation_root(pci_dev_get(to_pci_dev(dev)));
use_pdev:
ret = use_pdev_iommu_group(dma_pdev, dev);
pci_dev_put(dma_pdev);
return ret;
use_group:
return use_dev_data_iommu_group(dev_data->alias_data, dev);
return ivrs_alias;
}

static int iommu_init_device(struct device *dev)
Expand All @@ -441,7 +349,8 @@ static int iommu_init_device(struct device *dev)
if (!dev_data)
return -ENOMEM;

alias = amd_iommu_alias_table[dev_data->devid];
alias = get_alias(dev);

if (alias != dev_data->devid) {
struct iommu_dev_data *alias_data;

Expand Down Expand Up @@ -470,6 +379,9 @@ static int iommu_init_device(struct device *dev)

dev->archdata.iommu = dev_data;

iommu_device_link(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev,
dev);

return 0;
}

Expand All @@ -489,12 +401,22 @@ static void iommu_ignore_device(struct device *dev)

static void iommu_uninit_device(struct device *dev)
{
struct iommu_dev_data *dev_data = search_dev_data(get_device_id(dev));

if (!dev_data)
return;

iommu_device_unlink(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev,
dev);

iommu_group_remove_device(dev);

/* Unlink from alias, it may change if another device is re-plugged */
dev_data->alias_data = NULL;

/*
* Nothing to do here - we keep dev_data around for unplugged devices
* and reuse it when the device is re-plugged - not doing so would
* introduce a ton of races.
* We keep dev_data around for unplugged devices and reuse it when the
* device is re-plugged - not doing so would introduce a ton of races.
*/
}

Expand Down Expand Up @@ -3473,7 +3395,7 @@ static int amd_iommu_domain_has_cap(struct iommu_domain *domain,
return 0;
}

static struct iommu_ops amd_iommu_ops = {
static const struct iommu_ops amd_iommu_ops = {
.domain_init = amd_iommu_domain_init,
.domain_destroy = amd_iommu_domain_destroy,
.attach_dev = amd_iommu_attach_device,
Expand Down
Loading

0 comments on commit cbb24a2

Please sign in to comment.