From: Jason Gunthorpe <jgg@nvidia.com>
To: Robin Murphy <robin.murphy@arm.com>
Cc: joro@8bytes.org, will@kernel.org, iommu@lists.linux.dev,
linux-kernel@vger.kernel.org, hch@lst.de,
baolu.lu@linux.intel.com
Subject: Re: [PATCH v2 4/8] iommu: Factor out some helpers
Date: Mon, 30 Jan 2023 12:38:01 -0400 [thread overview]
Message-ID: <Y9fyaRGp7Q8E5to2@nvidia.com> (raw)
In-Reply-To: <959a1e8d598c0a82f94123e017cafb273784f848.1674753627.git.robin.murphy@arm.com>
On Thu, Jan 26, 2023 at 06:26:19PM +0000, Robin Murphy wrote:
> The pattern for picking the first device out of the group list is
> repeated a few times now, so it's clearly worth factoring out to hide
> the group_device detail from places that don't need to know. Similarly,
> the safety check for dev_iommu_ops() at public interfaces starts looking
> a bit repetitive, and might not be completely obvious at first glance,
> so let's factor that out for clarity as well, in preparation for more
> uses of both.
>
> Signed-off-by: Robin Murphy <robin.murphy@arm.com>
> ---
>
> v2: - Add dev_iommu_ops_valid() helper
> - Add lockdep assertion [Jason]
>
> drivers/iommu/iommu.c | 39 ++++++++++++++++++++++-----------------
> 1 file changed, 22 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index 77f076030995..440bb3b7bded 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -284,6 +284,12 @@ static void dev_iommu_free(struct device *dev)
> kfree(param);
> }
>
> +/* Only needed in public APIs which allow unchecked devices for convenience */
> +static bool dev_iommu_ops_valid(struct device *dev)
> +{
> + return dev->iommu && dev->iommu->iommu_dev;
> +}
I did an audit and more than half the callers need this test, and a
few places are missing it already.
We've kind of made a systematic error that assumes being in a group is
sufficient to prove there are non-NULL ops.
So I suggest that we make dev_iommu_ops() return NULL in all cases
where there is no driver and have a new API to get the ops in cases
where the call chain knows that it is safe, and there are only 5 of
those cases.
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index de91dd88705bd3..4b04ad50de8d6b 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -98,7 +98,8 @@ static int __iommu_group_set_domain(struct iommu_group *group,
struct iommu_domain *new_domain);
static int iommu_create_device_direct_mappings(struct iommu_group *group,
struct device *dev);
-static struct iommu_group *iommu_group_get_for_dev(struct device *dev);
+static struct iommu_group *iommu_group_get_for_dev(struct device *dev,
+ const struct iommu_ops *ops);
static ssize_t iommu_group_store_type(struct iommu_group *group,
const char *buf, size_t count);
@@ -130,6 +131,19 @@ static struct bus_type * const iommu_buses[] = {
#endif
};
+/*
+ * This may only be called if it is already clear from the calling context that
+ * the device has an ops. Seeing the device is part of an iommu_group is not
+ * enough as VFIO and POWER put devices in iommu_groups and do not attach
+ * drivers. Thus the only places that are safe have either already called
+ * dev_iommu_ops() on their call chain, or were responsible for assigning ops in
+ * the first place.
+ */
+static inline const struct iommu_ops *dev_iommu_ops_safe(struct device *dev)
+{
+ return dev->iommu->iommu_dev->ops;
+}
+
/*
* Use a function instead of an array here because the domain-type is a
* bit-field, so an array would waste memory.
@@ -338,7 +352,7 @@ static int __iommu_probe_device(struct device *dev, struct list_head *group_list
dev->iommu->iommu_dev = iommu_dev;
dev->iommu->max_pasids = dev_iommu_get_max_pasids(dev);
- group = iommu_group_get_for_dev(dev);
+ group = iommu_group_get_for_dev(dev, ops);
if (IS_ERR(group)) {
ret = PTR_ERR(group);
goto out_release;
@@ -414,7 +428,8 @@ int iommu_probe_device(struct device *dev)
mutex_unlock(&group->mutex);
iommu_group_put(group);
- ops = dev_iommu_ops(dev);
+ /* __iommu_probe_device() set the ops */
+ ops = dev_iommu_ops_safe(dev);
if (ops->probe_finalize)
ops->probe_finalize(dev);
@@ -430,14 +445,13 @@ int iommu_probe_device(struct device *dev)
void iommu_release_device(struct device *dev)
{
- const struct iommu_ops *ops;
+ const struct iommu_ops *ops = dev_iommu_ops(dev);
- if (!dev->iommu)
+ if (!ops)
return;
iommu_device_unlink(dev->iommu->iommu_dev, dev);
- ops = dev_iommu_ops(dev);
if (ops->release_device)
ops->release_device(dev);
@@ -614,13 +628,6 @@ int iommu_get_group_resv_regions(struct iommu_group *group,
list_for_each_entry(device, &group->devices, list) {
struct list_head dev_resv_regions;
- /*
- * Non-API groups still expose reserved_regions in sysfs,
- * so filter out calls that get here that way.
- */
- if (!device->dev->iommu)
- break;
-
INIT_LIST_HEAD(&dev_resv_regions);
iommu_get_resv_regions(device->dev, &dev_resv_regions);
ret = iommu_insert_device_resv_regions(&dev_resv_regions, head);
@@ -1332,7 +1339,8 @@ int iommu_page_response(struct device *dev,
struct iommu_fault_event *evt;
struct iommu_fault_page_request *prm;
struct dev_iommu *param = dev->iommu;
- const struct iommu_ops *ops = dev_iommu_ops(dev);
+ /* This API should only be called from an IOMMU driver */
+ const struct iommu_ops *ops = dev_iommu_ops_safe(dev);
bool has_pasid = msg->flags & IOMMU_PAGE_RESP_PASID_VALID;
if (!ops->page_response)
@@ -1601,7 +1609,8 @@ EXPORT_SYMBOL_GPL(fsl_mc_device_group);
static int iommu_get_def_domain_type(struct device *dev)
{
- const struct iommu_ops *ops = dev_iommu_ops(dev);
+ /* It is not locked but all callers know there is an ops */
+ const struct iommu_ops *ops = dev_iommu_ops_safe(dev);
if (dev_is_pci(dev) && to_pci_dev(dev)->untrusted)
return IOMMU_DOMAIN_DMA;
@@ -1658,9 +1667,9 @@ static int iommu_alloc_default_domain(struct iommu_group *group,
* to the returned IOMMU group, which will already include the provided
* device. The reference should be released with iommu_group_put().
*/
-static struct iommu_group *iommu_group_get_for_dev(struct device *dev)
+static struct iommu_group *iommu_group_get_for_dev(struct device *dev,
+ const struct iommu_ops *ops)
{
- const struct iommu_ops *ops = dev_iommu_ops(dev);
struct iommu_group *group;
int ret;
@@ -1795,7 +1804,8 @@ static int __iommu_group_dma_attach(struct iommu_group *group)
static int iommu_group_do_probe_finalize(struct device *dev, void *data)
{
- const struct iommu_ops *ops = dev_iommu_ops(dev);
+ /* It is unlocked but all callers know there is an ops */
+ const struct iommu_ops *ops = dev_iommu_ops_safe(dev);
if (ops->probe_finalize)
ops->probe_finalize(dev);
@@ -1884,13 +1894,9 @@ EXPORT_SYMBOL_GPL(iommu_present);
*/
bool device_iommu_capable(struct device *dev, enum iommu_cap cap)
{
- const struct iommu_ops *ops;
-
- if (!dev->iommu || !dev->iommu->iommu_dev)
- return false;
+ const struct iommu_ops *ops = dev_iommu_ops(dev);
- ops = dev_iommu_ops(dev);
- if (!ops->capable)
+ if (!ops || !ops->capable)
return false;
return ops->capable(dev, cap);
@@ -2620,7 +2626,11 @@ void iommu_get_resv_regions(struct device *dev, struct list_head *list)
{
const struct iommu_ops *ops = dev_iommu_ops(dev);
- if (ops->get_resv_regions)
+ /*
+ * Non-API groups still expose reserved_regions in sysfs, so filter out
+ * calls that get here that way.
+ */
+ if (ops && ops->get_resv_regions)
ops->get_resv_regions(dev, list);
}
@@ -2979,6 +2989,11 @@ static ssize_t iommu_group_store_type(struct iommu_group *group,
/* Since group has only one device */
grp_dev = list_first_entry(&group->devices, struct group_device, list);
dev = grp_dev->dev;
+ if (!dev_iommu_ops(dev)) {
+ /* The group doesn't have an iommu driver attached */
+ mutex_unlock(&group->mutex);
+ return -EINVAL;
+ }
get_device(dev);
/*
@@ -3301,7 +3316,7 @@ static void __iommu_remove_group_pasid(struct iommu_group *group,
const struct iommu_ops *ops;
list_for_each_entry(device, &group->devices, list) {
- ops = dev_iommu_ops(device->dev);
+ ops = dev_iommu_ops_safe(device->dev);
ops->remove_dev_pasid(device->dev, pasid);
}
}
@@ -3413,6 +3428,9 @@ struct iommu_domain *iommu_sva_domain_alloc(struct device *dev,
const struct iommu_ops *ops = dev_iommu_ops(dev);
struct iommu_domain *domain;
+ if (!ops)
+ return NULL;
+
domain = ops->domain_alloc(IOMMU_DOMAIN_SVA);
if (!domain)
return NULL;
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 46e1347bfa2286..60e84f8c7c46e9 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -441,14 +441,11 @@ static inline void iommu_iotlb_gather_init(struct iommu_iotlb_gather *gather)
};
}
+/* May return NULL if the device has no iommu driver */
static inline const struct iommu_ops *dev_iommu_ops(struct device *dev)
{
- /*
- * Assume that valid ops must be installed if iommu_probe_device()
- * has succeeded. The device ops are essentially for internal use
- * within the IOMMU subsystem itself, so we should be able to trust
- * ourselves not to misuse the helper.
- */
+ if (!dev->iommu)
+ return NULL;
return dev->iommu->iommu_dev->ops;
}
next prev parent reply other threads:[~2023-01-30 16:38 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-01-26 18:26 [PATCH v2 0/8] iommu: The early demise of bus ops Robin Murphy
2023-01-26 18:26 ` [PATCH v2 1/8] iommu: Decouple iommu_present() from " Robin Murphy
2023-01-28 7:55 ` Baolu Lu
2023-01-30 17:31 ` Jason Gunthorpe
2023-01-30 18:21 ` Robin Murphy
2023-01-26 18:26 ` [PATCH v2 2/8] iommu: Validate that devices match domains Robin Murphy
2023-01-28 8:04 ` Baolu Lu
2023-01-30 15:59 ` Jason Gunthorpe
2023-01-26 18:26 ` [PATCH v2 3/8] iommu: Add lockdep annotations for group list iterators Robin Murphy
2023-01-28 8:08 ` Baolu Lu
2023-01-28 12:20 ` Baolu Lu
2023-01-30 14:59 ` Robin Murphy
2023-01-26 18:26 ` [PATCH v2 4/8] iommu: Factor out some helpers Robin Murphy
2023-01-28 8:12 ` Baolu Lu
2023-01-30 16:38 ` Jason Gunthorpe [this message]
2023-01-30 18:05 ` Robin Murphy
2023-01-30 18:20 ` Jason Gunthorpe
2023-01-30 23:33 ` Robin Murphy
2023-01-31 19:54 ` Jason Gunthorpe
2023-01-26 18:26 ` [PATCH v2 5/8] iommu: Switch __iommu_domain_alloc() to device ops Robin Murphy
2023-01-26 23:22 ` Jacob Pan
2023-01-27 11:42 ` Robin Murphy
2023-01-27 16:32 ` Jacob Pan
2023-01-28 8:21 ` Baolu Lu
2023-01-30 17:51 ` Jason Gunthorpe
2023-01-26 18:26 ` [PATCH v2 6/8] iommu/arm-smmu: Don't register fwnode for legacy binding Robin Murphy
2023-01-30 17:14 ` Jason Gunthorpe
2023-01-26 18:26 ` [PATCH v2 7/8] iommu: Retire bus ops Robin Murphy
2023-01-28 12:10 ` Baolu Lu
2023-01-28 12:55 ` kernel test robot
2023-01-30 14:20 ` Jason Gunthorpe
2023-01-30 17:09 ` Jason Gunthorpe
2023-01-26 18:26 ` [PATCH v2 8/8] iommu: Clean up open-coded ownership checks Robin Murphy
2023-01-30 17:10 ` Jason Gunthorpe
2023-01-30 6:45 ` [PATCH v2 0/8] iommu: The early demise of bus ops Christoph Hellwig
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Y9fyaRGp7Q8E5to2@nvidia.com \
--to=jgg@nvidia.com \
--cc=baolu.lu@linux.intel.com \
--cc=hch@lst.de \
--cc=iommu@lists.linux.dev \
--cc=joro@8bytes.org \
--cc=linux-kernel@vger.kernel.org \
--cc=robin.murphy@arm.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox