Re: [Patch Part3 V5 3/8] iommu/vt-d: Implement DMAR unit hotplug framework

From: Yijing Wang
Date: Sun Sep 14 2014 - 21:41:03 EST


On 2014/9/12 10:10, Jiang Liu wrote:
> On Intel platforms, an IO Hub (PCI/PCIe host bridge) may contain DMAR
> units, so we need to support DMAR hotplug when supporting PCI host
> bridge hotplug on Intel platforms.
>
> According to Section 8.8 "Remapping Hardware Unit Hot Plug" in "Intel
> Virtualization Technology for Directed IO Architecture Specification
> Rev 2.2", ACPI BIOS should implement ACPI _DSM method under the ACPI
> object for the PCI host bridge to support DMAR hotplug.
>
> This patch introduces interfaces to parse ACPI _DSM method for
> DMAR unit hotplug. It also implements state machines for DMAR unit
> hot-addition and hot-removal.
>
> The PCI host bridge hotplug driver should call dmar_hotplug_hotplug()
> before scanning PCI devices connected for hot-addition and after
> destroying all PCI devices for hot-removal.
>

Reviewed-by: Yijing Wang <wangyijing@xxxxxxxxxx>


> Signed-off-by: Jiang Liu <jiang.liu@xxxxxxxxxxxxxxx>
> ---
> drivers/iommu/dmar.c | 268 +++++++++++++++++++++++++++++++++--
> drivers/iommu/intel-iommu.c | 78 +++++++++-
> drivers/iommu/intel_irq_remapping.c | 5 +
> include/linux/dmar.h | 33 +++++
> 4 files changed, 370 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c
> index b3405c50627f..e77b5d3f2f5c 100644
> --- a/drivers/iommu/dmar.c
> +++ b/drivers/iommu/dmar.c
> @@ -75,7 +75,7 @@ static unsigned long dmar_seq_ids[BITS_TO_LONGS(DMAR_UNITS_SUPPORTED)];
> static int alloc_iommu(struct dmar_drhd_unit *drhd);
> static void free_iommu(struct intel_iommu *iommu);
>
> -static void __init dmar_register_drhd_unit(struct dmar_drhd_unit *drhd)
> +static void dmar_register_drhd_unit(struct dmar_drhd_unit *drhd)
> {
> /*
> * add INCLUDE_ALL at the tail, so scan the list will find it at
> @@ -336,24 +336,45 @@ static struct notifier_block dmar_pci_bus_nb = {
> .priority = INT_MIN,
> };
>
> +static struct dmar_drhd_unit *
> +dmar_find_dmaru(struct acpi_dmar_hardware_unit *drhd)
> +{
> + struct dmar_drhd_unit *dmaru;
> +
> + list_for_each_entry_rcu(dmaru, &dmar_drhd_units, list)
> + if (dmaru->segment == drhd->segment &&
> + dmaru->reg_base_addr == drhd->address)
> + return dmaru;
> +
> + return NULL;
> +}
> +
> /**
> * dmar_parse_one_drhd - parses exactly one DMA remapping hardware definition
> * structure which uniquely represent one DMA remapping hardware unit
> * present in the platform
> */
> -static int __init
> -dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
> +static int dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
> {
> struct acpi_dmar_hardware_unit *drhd;
> struct dmar_drhd_unit *dmaru;
> int ret = 0;
>
> drhd = (struct acpi_dmar_hardware_unit *)header;
> - dmaru = kzalloc(sizeof(*dmaru), GFP_KERNEL);
> + dmaru = dmar_find_dmaru(drhd);
> + if (dmaru)
> + goto out;
> +
> + dmaru = kzalloc(sizeof(*dmaru) + header->length, GFP_KERNEL);
> if (!dmaru)
> return -ENOMEM;
>
> - dmaru->hdr = header;
> + /*
> + * If header is allocated from slab by ACPI _DSM method, we need to
> + * copy the content because the memory buffer will be freed on return.
> + */
> + dmaru->hdr = (void *)(dmaru + 1);
> + memcpy(dmaru->hdr, header, header->length);
> dmaru->reg_base_addr = drhd->address;
> dmaru->segment = drhd->segment;
> dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */
> @@ -374,6 +395,7 @@ dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
> }
> dmar_register_drhd_unit(dmaru);
>
> +out:
> if (arg)
> (*(int *)arg)++;
>
> @@ -411,8 +433,7 @@ static int __init dmar_parse_one_andd(struct acpi_dmar_header *header,
> }
>
> #ifdef CONFIG_ACPI_NUMA
> -static int __init
> -dmar_parse_one_rhsa(struct acpi_dmar_header *header, void *arg)
> +static int dmar_parse_one_rhsa(struct acpi_dmar_header *header, void *arg)
> {
> struct acpi_dmar_rhsa *rhsa;
> struct dmar_drhd_unit *drhd;
> @@ -805,14 +826,22 @@ dmar_validate_one_drhd(struct acpi_dmar_header *entry, void *arg)
> return -EINVAL;
> }
>
> - addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
> + if (arg)
> + addr = ioremap(drhd->address, VTD_PAGE_SIZE);
> + else
> + addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
> if (!addr) {
> pr_warn("IOMMU: can't validate: %llx\n", drhd->address);
> return -EINVAL;
> }
> +
> cap = dmar_readq(addr + DMAR_CAP_REG);
> ecap = dmar_readq(addr + DMAR_ECAP_REG);
> - early_iounmap(addr, VTD_PAGE_SIZE);
> +
> + if (arg)
> + iounmap(addr);
> + else
> + early_iounmap(addr, VTD_PAGE_SIZE);
>
> if (cap == (uint64_t)-1 && ecap == (uint64_t)-1) {
> warn_invalid_dmar(drhd->address, " returns all ones");
> @@ -1686,12 +1715,17 @@ int __init dmar_ir_support(void)
> return dmar->flags & 0x1;
> }
>
> +/* Check whether DMAR units are in use */
> +static inline bool dmar_in_use(void)
> +{
> + return irq_remapping_enabled || intel_iommu_enabled;
> +}
> +
> static int __init dmar_free_unused_resources(void)
> {
> struct dmar_drhd_unit *dmaru, *dmaru_n;
>
> - /* DMAR units are in use */
> - if (irq_remapping_enabled || intel_iommu_enabled)
> + if (dmar_in_use())
> return 0;
>
> if (dmar_dev_scope_status != 1 && !list_empty(&dmar_drhd_units))
> @@ -1709,3 +1743,215 @@ static int __init dmar_free_unused_resources(void)
>
> late_initcall(dmar_free_unused_resources);
> IOMMU_INIT_POST(detect_intel_iommu);
> +
> +/*
> + * DMAR Hotplug Support
> + * For more details, please refer to Intel(R) Virtualization Technology
> + * for Directed-IO Architecture Specifiction, Rev 2.2, Section 8.8
> + * "Remapping Hardware Unit Hot Plug".
> + */
> +static u8 dmar_hp_uuid[] = {
> + /* 0000 */ 0xA6, 0xA3, 0xC1, 0xD8, 0x9B, 0xBE, 0x9B, 0x4C,
> + /* 0008 */ 0x91, 0xBF, 0xC3, 0xCB, 0x81, 0xFC, 0x5D, 0xAF
> +};
> +
> +/*
> + * Currently there's only one revision and BIOS will not check the revision id,
> + * so use 0 for safety.
> + */
> +#define DMAR_DSM_REV_ID 0
> +#define DMAR_DSM_FUNC_DRHD 1
> +#define DMAR_DSM_FUNC_ATSR 2
> +#define DMAR_DSM_FUNC_RHSA 3
> +
> +static inline bool dmar_detect_dsm(acpi_handle handle, int func)
> +{
> + return acpi_check_dsm(handle, dmar_hp_uuid, DMAR_DSM_REV_ID, 1 << func);
> +}
> +
> +static int dmar_walk_dsm_resource(acpi_handle handle, int func,
> + dmar_res_handler_t handler, void *arg)
> +{
> + int ret = -ENODEV;
> + union acpi_object *obj;
> + struct acpi_dmar_header *start;
> + struct dmar_res_callback callback;
> + static int res_type[] = {
> + [DMAR_DSM_FUNC_DRHD] = ACPI_DMAR_TYPE_HARDWARE_UNIT,
> + [DMAR_DSM_FUNC_ATSR] = ACPI_DMAR_TYPE_ROOT_ATS,
> + [DMAR_DSM_FUNC_RHSA] = ACPI_DMAR_TYPE_HARDWARE_AFFINITY,
> + };
> +
> + if (!dmar_detect_dsm(handle, func))
> + return 0;
> +
> + obj = acpi_evaluate_dsm_typed(handle, dmar_hp_uuid, DMAR_DSM_REV_ID,
> + func, NULL, ACPI_TYPE_BUFFER);
> + if (!obj)
> + return -ENODEV;
> +
> + memset(&callback, 0, sizeof(callback));
> + callback.cb[res_type[func]] = handler;
> + callback.arg[res_type[func]] = arg;
> + start = (struct acpi_dmar_header *)obj->buffer.pointer;
> + ret = dmar_walk_resources(start, obj->buffer.length, &callback);
> +
> + ACPI_FREE(obj);
> +
> + return ret;
> +}
> +
> +static int dmar_hp_add_drhd(struct acpi_dmar_header *header, void *arg)
> +{
> + int ret;
> + struct dmar_drhd_unit *dmaru;
> +
> + dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
> + if (!dmaru)
> + return -ENODEV;
> +
> + ret = dmar_ir_hotplug(dmaru, true);
> + if (ret == 0)
> + ret = dmar_iommu_hotplug(dmaru, true);
> +
> + return ret;
> +}
> +
> +static int dmar_hp_remove_drhd(struct acpi_dmar_header *header, void *arg)
> +{
> + int i, ret;
> + struct device *dev;
> + struct dmar_drhd_unit *dmaru;
> +
> + dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
> + if (!dmaru)
> + return 0;
> +
> + /*
> + * All PCI devices managed by this unit should have been destroyed.
> + */
> + if (!dmaru->include_all && dmaru->devices && dmaru->devices_cnt)
> + for_each_active_dev_scope(dmaru->devices,
> + dmaru->devices_cnt, i, dev)
> + return -EBUSY;
> +
> + ret = dmar_ir_hotplug(dmaru, false);
> + if (ret == 0)
> + ret = dmar_iommu_hotplug(dmaru, false);
> +
> + return ret;
> +}
> +
> +static int dmar_hp_release_drhd(struct acpi_dmar_header *header, void *arg)
> +{
> + struct dmar_drhd_unit *dmaru;
> +
> + dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
> + if (dmaru) {
> + list_del_rcu(&dmaru->list);
> + synchronize_rcu();
> + dmar_free_drhd(dmaru);
> + }
> +
> + return 0;
> +}
> +
> +static int dmar_hotplug_insert(acpi_handle handle)
> +{
> + int ret;
> + int drhd_count = 0;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_validate_one_drhd, (void *)1);
> + if (ret)
> + goto out;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_parse_one_drhd, (void *)&drhd_count);
> + if (ret == 0 && drhd_count == 0) {
> + pr_warn(FW_BUG "No DRHD structures in buffer returned by _DSM method\n");
> + goto out;
> + } else if (ret) {
> + goto release_drhd;
> + }
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_RHSA,
> + &dmar_parse_one_rhsa, NULL);
> + if (ret)
> + goto release_drhd;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
> + &dmar_parse_one_atsr, NULL);
> + if (ret)
> + goto release_atsr;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_add_drhd, NULL);
> + if (!ret)
> + return 0;
> +
> + dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_remove_drhd, NULL);
> +release_atsr:
> + dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
> + &dmar_release_one_atsr, NULL);
> +release_drhd:
> + dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_release_drhd, NULL);
> +out:
> + return ret;
> +}
> +
> +static int dmar_hotplug_remove(acpi_handle handle)
> +{
> + int ret;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
> + &dmar_check_one_atsr, NULL);
> + if (ret)
> + return ret;
> +
> + ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_remove_drhd, NULL);
> + if (ret == 0) {
> + WARN_ON(dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
> + &dmar_release_one_atsr, NULL));
> + WARN_ON(dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_release_drhd, NULL));
> + } else {
> + dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
> + &dmar_hp_add_drhd, NULL);
> + }
> +
> + return ret;
> +}
> +
> +static int dmar_device_hotplug(acpi_handle handle, bool insert)
> +{
> + int ret;
> +
> + if (!dmar_in_use())
> + return 0;
> +
> + if (!dmar_detect_dsm(handle, DMAR_DSM_FUNC_DRHD))
> + return 0;
> +
> + down_write(&dmar_global_lock);
> + if (insert)
> + ret = dmar_hotplug_insert(handle);
> + else
> + ret = dmar_hotplug_remove(handle);
> + up_write(&dmar_global_lock);
> +
> + return ret;
> +}
> +
> +int dmar_device_add(acpi_handle handle)
> +{
> + return dmar_device_hotplug(handle, true);
> +}
> +
> +int dmar_device_remove(acpi_handle handle)
> +{
> + return dmar_device_hotplug(handle, false);
> +}
> diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
> index 7daa74ed46d0..70d9d47eaeda 100644
> --- a/drivers/iommu/intel-iommu.c
> +++ b/drivers/iommu/intel-iommu.c
> @@ -3701,17 +3701,48 @@ int __init dmar_parse_one_rmrr(struct acpi_dmar_header *header, void *arg)
> return 0;
> }
>
> -int __init dmar_parse_one_atsr(struct acpi_dmar_header *hdr, void *arg)
> +static struct dmar_atsr_unit *dmar_find_atsr(struct acpi_dmar_atsr *atsr)
> +{
> + struct dmar_atsr_unit *atsru;
> + struct acpi_dmar_atsr *tmp;
> +
> + list_for_each_entry_rcu(atsru, &dmar_atsr_units, list) {
> + tmp = (struct acpi_dmar_atsr *)atsru->hdr;
> + if (atsr->segment != tmp->segment)
> + continue;
> + if (atsr->header.length != tmp->header.length)
> + continue;
> + if (memcmp(atsr, tmp, atsr->header.length) == 0)
> + return atsru;
> + }
> +
> + return NULL;
> +}
> +
> +int dmar_parse_one_atsr(struct acpi_dmar_header *hdr, void *arg)
> {
> struct acpi_dmar_atsr *atsr;
> struct dmar_atsr_unit *atsru;
>
> + if (system_state != SYSTEM_BOOTING && !intel_iommu_enabled)
> + return 0;
> +
> atsr = container_of(hdr, struct acpi_dmar_atsr, header);
> - atsru = kzalloc(sizeof(*atsru), GFP_KERNEL);
> + atsru = dmar_find_atsr(atsr);
> + if (atsru)
> + return 0;
> +
> + atsru = kzalloc(sizeof(*atsru) + hdr->length, GFP_KERNEL);
> if (!atsru)
> return -ENOMEM;
>
> - atsru->hdr = hdr;
> + /*
> + * If memory is allocated from slab by ACPI _DSM method, we need to
> + * copy the memory content because the memory buffer will be freed
> + * on return.
> + */
> + atsru->hdr = (void *)(atsru + 1);
> + memcpy(atsru->hdr, hdr, hdr->length);
> atsru->include_all = atsr->flags & 0x1;
> if (!atsru->include_all) {
> atsru->devices = dmar_alloc_dev_scope((void *)(atsr + 1),
> @@ -3734,6 +3765,47 @@ static void intel_iommu_free_atsr(struct dmar_atsr_unit *atsru)
> kfree(atsru);
> }
>
> +int dmar_release_one_atsr(struct acpi_dmar_header *hdr, void *arg)
> +{
> + struct acpi_dmar_atsr *atsr;
> + struct dmar_atsr_unit *atsru;
> +
> + atsr = container_of(hdr, struct acpi_dmar_atsr, header);
> + atsru = dmar_find_atsr(atsr);
> + if (atsru) {
> + list_del_rcu(&atsru->list);
> + synchronize_rcu();
> + intel_iommu_free_atsr(atsru);
> + }
> +
> + return 0;
> +}
> +
> +int dmar_check_one_atsr(struct acpi_dmar_header *hdr, void *arg)
> +{
> + int i;
> + struct device *dev;
> + struct acpi_dmar_atsr *atsr;
> + struct dmar_atsr_unit *atsru;
> +
> + atsr = container_of(hdr, struct acpi_dmar_atsr, header);
> + atsru = dmar_find_atsr(atsr);
> + if (!atsru)
> + return 0;
> +
> + if (!atsru->include_all && atsru->devices && atsru->devices_cnt)
> + for_each_active_dev_scope(atsru->devices, atsru->devices_cnt,
> + i, dev)
> + return -EBUSY;
> +
> + return 0;
> +}
> +
> +int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
> +{
> + return intel_iommu_enabled ? -ENOSYS : 0;
> +}
> +
> static void intel_iommu_free_dmars(void)
> {
> struct dmar_rmrr_unit *rmrru, *rmrr_n;
> diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
> index 0df41f6264f5..9b140ed854ec 100644
> --- a/drivers/iommu/intel_irq_remapping.c
> +++ b/drivers/iommu/intel_irq_remapping.c
> @@ -1172,3 +1172,8 @@ struct irq_remap_ops intel_irq_remap_ops = {
> .msi_setup_irq = intel_msi_setup_irq,
> .setup_hpet_msi = intel_setup_hpet_msi,
> };
> +
> +int dmar_ir_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
> +{
> + return irq_remapping_enabled ? -ENOSYS : 0;
> +}
> diff --git a/include/linux/dmar.h b/include/linux/dmar.h
> index c8a576bc3a98..594d4ac79e75 100644
> --- a/include/linux/dmar.h
> +++ b/include/linux/dmar.h
> @@ -120,6 +120,8 @@ extern int dmar_remove_dev_scope(struct dmar_pci_notify_info *info,
> /* Intel IOMMU detection */
> extern int detect_intel_iommu(void);
> extern int enable_drhd_fault_handling(void);
> +extern int dmar_device_add(acpi_handle handle);
> +extern int dmar_device_remove(acpi_handle handle);
>
> static inline int dmar_res_noop(struct acpi_dmar_header *hdr, void *arg)
> {
> @@ -131,17 +133,48 @@ extern int iommu_detected, no_iommu;
> extern int intel_iommu_init(void);
> extern int dmar_parse_one_rmrr(struct acpi_dmar_header *header, void *arg);
> extern int dmar_parse_one_atsr(struct acpi_dmar_header *header, void *arg);
> +extern int dmar_check_one_atsr(struct acpi_dmar_header *hdr, void *arg);
> +extern int dmar_release_one_atsr(struct acpi_dmar_header *hdr, void *arg);
> +extern int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert);
> extern int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info);
> #else /* !CONFIG_INTEL_IOMMU: */
> static inline int intel_iommu_init(void) { return -ENODEV; }
> +
> #define dmar_parse_one_rmrr dmar_res_noop
> #define dmar_parse_one_atsr dmar_res_noop
> +#define dmar_check_one_atsr dmar_res_noop
> +#define dmar_release_one_atsr dmar_res_noop
> +
> static inline int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info)
> {
> return 0;
> }
> +
> +static inline int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
> +{
> + return 0;
> +}
> #endif /* CONFIG_INTEL_IOMMU */
>
> +#ifdef CONFIG_IRQ_REMAP
> +extern int dmar_ir_hotplug(struct dmar_drhd_unit *dmaru, bool insert);
> +#else /* CONFIG_IRQ_REMAP */
> +static inline int dmar_ir_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
> +{ return 0; }
> +#endif /* CONFIG_IRQ_REMAP */
> +
> +#else /* CONFIG_DMAR_TABLE */
> +
> +static inline int dmar_device_add(void *handle)
> +{
> + return 0;
> +}
> +
> +static inline int dmar_device_remove(void *handle)
> +{
> + return 0;
> +}
> +
> #endif /* CONFIG_DMAR_TABLE */
>
> struct irte {
>


--
Thanks!
Yijing

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/