Merge tag 'iommu-updates-v6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/joro...
[linux-2.6-block.git] / drivers / iommu / iommufd / selftest.c
index ee6079847091027eed02fde1f64503a03592a6af..5d93434003d8ad666af55e212372e37b81c06895 100644 (file)
 static DECLARE_FAULT_ATTR(fail_iommufd);
 static struct dentry *dbgfs_root;
 static struct platform_device *selftest_iommu_dev;
+static const struct iommu_ops mock_ops;
+static struct iommu_domain_ops domain_nested_ops;
 
 size_t iommufd_test_memory_limit = 65536;
 
 enum {
+       MOCK_DIRTY_TRACK = 1,
        MOCK_IO_PAGE_SIZE = PAGE_SIZE / 2,
 
        /*
@@ -36,6 +39,7 @@ enum {
        _MOCK_PFN_START = MOCK_PFN_MASK + 1,
        MOCK_PFN_START_IOVA = _MOCK_PFN_START,
        MOCK_PFN_LAST_IOVA = _MOCK_PFN_START,
+       MOCK_PFN_DIRTY_IOVA = _MOCK_PFN_START << 1,
 };
 
 /*
@@ -86,16 +90,24 @@ void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
 }
 
 struct mock_iommu_domain {
+       unsigned long flags;
        struct iommu_domain domain;
        struct xarray pfns;
 };
 
+struct mock_iommu_domain_nested {
+       struct iommu_domain domain;
+       struct mock_iommu_domain *parent;
+       u32 iotlb[MOCK_NESTED_DOMAIN_IOTLB_NUM];
+};
+
 enum selftest_obj_type {
        TYPE_IDEV,
 };
 
 struct mock_dev {
        struct device dev;
+       unsigned long flags;
 };
 
 struct selftest_obj {
@@ -114,6 +126,11 @@ struct selftest_obj {
 static int mock_domain_nop_attach(struct iommu_domain *domain,
                                  struct device *dev)
 {
+       struct mock_dev *mdev = container_of(dev, struct mock_dev, dev);
+
+       if (domain->dirty_ops && (mdev->flags & MOCK_FLAGS_DEVICE_NO_DIRTY))
+               return -EINVAL;
+
        return 0;
 }
 
@@ -141,6 +158,67 @@ static void *mock_domain_hw_info(struct device *dev, u32 *length, u32 *type)
        return info;
 }
 
+static int mock_domain_set_dirty_tracking(struct iommu_domain *domain,
+                                         bool enable)
+{
+       struct mock_iommu_domain *mock =
+               container_of(domain, struct mock_iommu_domain, domain);
+       unsigned long flags = mock->flags;
+
+       if (enable && !domain->dirty_ops)
+               return -EINVAL;
+
+       /* No change? */
+       if (!(enable ^ !!(flags & MOCK_DIRTY_TRACK)))
+               return 0;
+
+       flags = (enable ? flags | MOCK_DIRTY_TRACK : flags & ~MOCK_DIRTY_TRACK);
+
+       mock->flags = flags;
+       return 0;
+}
+
+static int mock_domain_read_and_clear_dirty(struct iommu_domain *domain,
+                                           unsigned long iova, size_t size,
+                                           unsigned long flags,
+                                           struct iommu_dirty_bitmap *dirty)
+{
+       struct mock_iommu_domain *mock =
+               container_of(domain, struct mock_iommu_domain, domain);
+       unsigned long i, max = size / MOCK_IO_PAGE_SIZE;
+       void *ent, *old;
+
+       if (!(mock->flags & MOCK_DIRTY_TRACK) && dirty->bitmap)
+               return -EINVAL;
+
+       for (i = 0; i < max; i++) {
+               unsigned long cur = iova + i * MOCK_IO_PAGE_SIZE;
+
+               ent = xa_load(&mock->pfns, cur / MOCK_IO_PAGE_SIZE);
+               if (ent && (xa_to_value(ent) & MOCK_PFN_DIRTY_IOVA)) {
+                       /* Clear dirty */
+                       if (!(flags & IOMMU_DIRTY_NO_CLEAR)) {
+                               unsigned long val;
+
+                               val = xa_to_value(ent) & ~MOCK_PFN_DIRTY_IOVA;
+                               old = xa_store(&mock->pfns,
+                                              cur / MOCK_IO_PAGE_SIZE,
+                                              xa_mk_value(val), GFP_KERNEL);
+                               WARN_ON_ONCE(ent != old);
+                       }
+                       iommu_dirty_bitmap_record(dirty, cur,
+                                                 MOCK_IO_PAGE_SIZE);
+               }
+       }
+
+       return 0;
+}
+
+const struct iommu_dirty_ops dirty_ops = {
+       .set_dirty_tracking = mock_domain_set_dirty_tracking,
+       .read_and_clear_dirty = mock_domain_read_and_clear_dirty,
+};
+
 static struct iommu_domain *mock_domain_alloc_paging(struct device *dev)
 {
        struct mock_iommu_domain *mock;
@@ -151,10 +229,78 @@ static struct iommu_domain *mock_domain_alloc_paging(struct device *dev)
        mock->domain.geometry.aperture_start = MOCK_APERTURE_START;
        mock->domain.geometry.aperture_end = MOCK_APERTURE_LAST;
        mock->domain.pgsize_bitmap = MOCK_IO_PAGE_SIZE;
+       mock->domain.ops = mock_ops.default_domain_ops;
+       mock->domain.type = IOMMU_DOMAIN_UNMANAGED;
        xa_init(&mock->pfns);
        return &mock->domain;
 }
 
+static struct iommu_domain *
+__mock_domain_alloc_nested(struct mock_iommu_domain *mock_parent,
+                          const struct iommu_hwpt_selftest *user_cfg)
+{
+       struct mock_iommu_domain_nested *mock_nested;
+       int i;
+
+       mock_nested = kzalloc(sizeof(*mock_nested), GFP_KERNEL);
+       if (!mock_nested)
+               return ERR_PTR(-ENOMEM);
+       mock_nested->parent = mock_parent;
+       mock_nested->domain.ops = &domain_nested_ops;
+       mock_nested->domain.type = IOMMU_DOMAIN_NESTED;
+       for (i = 0; i < MOCK_NESTED_DOMAIN_IOTLB_NUM; i++)
+               mock_nested->iotlb[i] = user_cfg->iotlb;
+       return &mock_nested->domain;
+}
+
+static struct iommu_domain *
+mock_domain_alloc_user(struct device *dev, u32 flags,
+                      struct iommu_domain *parent,
+                      const struct iommu_user_data *user_data)
+{
+       struct mock_iommu_domain *mock_parent;
+       struct iommu_hwpt_selftest user_cfg;
+       int rc;
+
+       /* must be mock_domain */
+       if (!parent) {
+               struct mock_dev *mdev = container_of(dev, struct mock_dev, dev);
+               bool has_dirty_flag = flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING;
+               bool no_dirty_ops = mdev->flags & MOCK_FLAGS_DEVICE_NO_DIRTY;
+               struct iommu_domain *domain;
+
+               if (flags & (~(IOMMU_HWPT_ALLOC_NEST_PARENT |
+                              IOMMU_HWPT_ALLOC_DIRTY_TRACKING)))
+                       return ERR_PTR(-EOPNOTSUPP);
+               if (user_data || (has_dirty_flag && no_dirty_ops))
+                       return ERR_PTR(-EOPNOTSUPP);
+               domain = mock_domain_alloc_paging(NULL);
+               if (!domain)
+                       return ERR_PTR(-ENOMEM);
+               if (has_dirty_flag)
+                       container_of(domain, struct mock_iommu_domain, domain)
+                               ->domain.dirty_ops = &dirty_ops;
+               return domain;
+       }
+
+       /* must be mock_domain_nested */
+       if (user_data->type != IOMMU_HWPT_DATA_SELFTEST || flags)
+               return ERR_PTR(-EOPNOTSUPP);
+       if (!parent || parent->ops != mock_ops.default_domain_ops)
+               return ERR_PTR(-EINVAL);
+
+       mock_parent = container_of(parent, struct mock_iommu_domain, domain);
+       if (!mock_parent)
+               return ERR_PTR(-EINVAL);
+
+       rc = iommu_copy_struct_from_user(&user_cfg, user_data,
+                                        IOMMU_HWPT_DATA_SELFTEST, iotlb);
+       if (rc)
+               return ERR_PTR(rc);
+
+       return __mock_domain_alloc_nested(mock_parent, &user_cfg);
+}
+
 static void mock_domain_free(struct iommu_domain *domain)
 {
        struct mock_iommu_domain *mock =
@@ -232,7 +378,7 @@ static size_t mock_domain_unmap_pages(struct iommu_domain *domain,
 
                for (cur = 0; cur != pgsize; cur += MOCK_IO_PAGE_SIZE) {
                        ent = xa_erase(&mock->pfns, iova / MOCK_IO_PAGE_SIZE);
-                       WARN_ON(!ent);
+
                        /*
                         * iommufd generates unmaps that must be a strict
                         * superset of the map's performend So every starting
@@ -242,13 +388,13 @@ static size_t mock_domain_unmap_pages(struct iommu_domain *domain,
                         * passed to map_pages
                         */
                        if (first) {
-                               WARN_ON(!(xa_to_value(ent) &
-                                         MOCK_PFN_START_IOVA));
+                               WARN_ON(ent && !(xa_to_value(ent) &
+                                                MOCK_PFN_START_IOVA));
                                first = false;
                        }
                        if (pgcount == 1 && cur + MOCK_IO_PAGE_SIZE == pgsize)
-                               WARN_ON(!(xa_to_value(ent) &
-                                         MOCK_PFN_LAST_IOVA));
+                               WARN_ON(ent && !(xa_to_value(ent) &
+                                                MOCK_PFN_LAST_IOVA));
 
                        iova += MOCK_IO_PAGE_SIZE;
                        ret += MOCK_IO_PAGE_SIZE;
@@ -272,7 +418,18 @@ static phys_addr_t mock_domain_iova_to_phys(struct iommu_domain *domain,
 
 static bool mock_domain_capable(struct device *dev, enum iommu_cap cap)
 {
-       return cap == IOMMU_CAP_CACHE_COHERENCY;
+       struct mock_dev *mdev = container_of(dev, struct mock_dev, dev);
+
+       switch (cap) {
+       case IOMMU_CAP_CACHE_COHERENCY:
+               return true;
+       case IOMMU_CAP_DIRTY_TRACKING:
+               return !(mdev->flags & MOCK_FLAGS_DEVICE_NO_DIRTY);
+       default:
+               break;
+       }
+
+       return false;
 }
 
 static struct iommu_device mock_iommu_device = {
@@ -294,6 +451,7 @@ static const struct iommu_ops mock_ops = {
        .pgsize_bitmap = MOCK_IO_PAGE_SIZE,
        .hw_info = mock_domain_hw_info,
        .domain_alloc_paging = mock_domain_alloc_paging,
+       .domain_alloc_user = mock_domain_alloc_user,
        .capable = mock_domain_capable,
        .device_group = generic_device_group,
        .probe_device = mock_probe_device,
@@ -307,19 +465,41 @@ static const struct iommu_ops mock_ops = {
                },
 };
 
+static void mock_domain_free_nested(struct iommu_domain *domain)
+{
+       struct mock_iommu_domain_nested *mock_nested =
+               container_of(domain, struct mock_iommu_domain_nested, domain);
+
+       kfree(mock_nested);
+}
+
+static struct iommu_domain_ops domain_nested_ops = {
+       .free = mock_domain_free_nested,
+       .attach_dev = mock_domain_nop_attach,
+};
+
 static inline struct iommufd_hw_pagetable *
-get_md_pagetable(struct iommufd_ucmd *ucmd, u32 mockpt_id,
-                struct mock_iommu_domain **mock)
+__get_md_pagetable(struct iommufd_ucmd *ucmd, u32 mockpt_id, u32 hwpt_type)
 {
-       struct iommufd_hw_pagetable *hwpt;
        struct iommufd_object *obj;
 
-       obj = iommufd_get_object(ucmd->ictx, mockpt_id,
-                                IOMMUFD_OBJ_HW_PAGETABLE);
+       obj = iommufd_get_object(ucmd->ictx, mockpt_id, hwpt_type);
        if (IS_ERR(obj))
                return ERR_CAST(obj);
-       hwpt = container_of(obj, struct iommufd_hw_pagetable, obj);
-       if (hwpt->domain->ops != mock_ops.default_domain_ops) {
+       return container_of(obj, struct iommufd_hw_pagetable, obj);
+}
+
+static inline struct iommufd_hw_pagetable *
+get_md_pagetable(struct iommufd_ucmd *ucmd, u32 mockpt_id,
+                struct mock_iommu_domain **mock)
+{
+       struct iommufd_hw_pagetable *hwpt;
+
+       hwpt = __get_md_pagetable(ucmd, mockpt_id, IOMMUFD_OBJ_HWPT_PAGING);
+       if (IS_ERR(hwpt))
+               return hwpt;
+       if (hwpt->domain->type != IOMMU_DOMAIN_UNMANAGED ||
+           hwpt->domain->ops != mock_ops.default_domain_ops) {
                iommufd_put_object(&hwpt->obj);
                return ERR_PTR(-EINVAL);
        }
@@ -327,6 +507,25 @@ get_md_pagetable(struct iommufd_ucmd *ucmd, u32 mockpt_id,
        return hwpt;
 }
 
+static inline struct iommufd_hw_pagetable *
+get_md_pagetable_nested(struct iommufd_ucmd *ucmd, u32 mockpt_id,
+                       struct mock_iommu_domain_nested **mock_nested)
+{
+       struct iommufd_hw_pagetable *hwpt;
+
+       hwpt = __get_md_pagetable(ucmd, mockpt_id, IOMMUFD_OBJ_HWPT_NESTED);
+       if (IS_ERR(hwpt))
+               return hwpt;
+       if (hwpt->domain->type != IOMMU_DOMAIN_NESTED ||
+           hwpt->domain->ops != &domain_nested_ops) {
+               iommufd_put_object(&hwpt->obj);
+               return ERR_PTR(-EINVAL);
+       }
+       *mock_nested = container_of(hwpt->domain,
+                                   struct mock_iommu_domain_nested, domain);
+       return hwpt;
+}
+
 struct mock_bus_type {
        struct bus_type bus;
        struct notifier_block nb;
@@ -348,16 +547,20 @@ static void mock_dev_release(struct device *dev)
        kfree(mdev);
 }
 
-static struct mock_dev *mock_dev_create(void)
+static struct mock_dev *mock_dev_create(unsigned long dev_flags)
 {
        struct mock_dev *mdev;
        int rc;
 
+       if (dev_flags & ~(MOCK_FLAGS_DEVICE_NO_DIRTY))
+               return ERR_PTR(-EINVAL);
+
        mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
        if (!mdev)
                return ERR_PTR(-ENOMEM);
 
        device_initialize(&mdev->dev);
+       mdev->flags = dev_flags;
        mdev->dev.release = mock_dev_release;
        mdev->dev.bus = &iommufd_mock_bus_type.bus;
 
@@ -393,6 +596,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
        struct iommufd_device *idev;
        struct selftest_obj *sobj;
        u32 pt_id = cmd->id;
+       u32 dev_flags = 0;
        u32 idev_id;
        int rc;
 
@@ -403,7 +607,10 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
        sobj->idev.ictx = ucmd->ictx;
        sobj->type = TYPE_IDEV;
 
-       sobj->idev.mock_dev = mock_dev_create();
+       if (cmd->op == IOMMU_TEST_OP_MOCK_DOMAIN_FLAGS)
+               dev_flags = cmd->mock_domain_flags.dev_flags;
+
+       sobj->idev.mock_dev = mock_dev_create(dev_flags);
        if (IS_ERR(sobj->idev.mock_dev)) {
                rc = PTR_ERR(sobj->idev.mock_dev);
                goto out_sobj;
@@ -963,6 +1170,73 @@ static_assert((unsigned int)MOCK_ACCESS_RW_WRITE == IOMMUFD_ACCESS_RW_WRITE);
 static_assert((unsigned int)MOCK_ACCESS_RW_SLOW_PATH ==
              __IOMMUFD_ACCESS_RW_SLOW_PATH);
 
+static int iommufd_test_dirty(struct iommufd_ucmd *ucmd, unsigned int mockpt_id,
+                             unsigned long iova, size_t length,
+                             unsigned long page_size, void __user *uptr,
+                             u32 flags)
+{
+       unsigned long bitmap_size, i, max;
+       struct iommu_test_cmd *cmd = ucmd->cmd;
+       struct iommufd_hw_pagetable *hwpt;
+       struct mock_iommu_domain *mock;
+       int rc, count = 0;
+       void *tmp;
+
+       if (!page_size || !length || iova % page_size || length % page_size ||
+           !uptr)
+               return -EINVAL;
+
+       hwpt = get_md_pagetable(ucmd, mockpt_id, &mock);
+       if (IS_ERR(hwpt))
+               return PTR_ERR(hwpt);
+
+       if (!(mock->flags & MOCK_DIRTY_TRACK)) {
+               rc = -EINVAL;
+               goto out_put;
+       }
+
+       max = length / page_size;
+       bitmap_size = max / BITS_PER_BYTE;
+
+       tmp = kvzalloc(bitmap_size, GFP_KERNEL_ACCOUNT);
+       if (!tmp) {
+               rc = -ENOMEM;
+               goto out_put;
+       }
+
+       if (copy_from_user(tmp, uptr, bitmap_size)) {
+               rc = -EFAULT;
+               goto out_free;
+       }
+
+       for (i = 0; i < max; i++) {
+               unsigned long cur = iova + i * page_size;
+               void *ent, *old;
+
+               if (!test_bit(i, (unsigned long *)tmp))
+                       continue;
+
+               ent = xa_load(&mock->pfns, cur / page_size);
+               if (ent) {
+                       unsigned long val;
+
+                       val = xa_to_value(ent) | MOCK_PFN_DIRTY_IOVA;
+                       old = xa_store(&mock->pfns, cur / page_size,
+                                      xa_mk_value(val), GFP_KERNEL);
+                       WARN_ON_ONCE(ent != old);
+                       count++;
+               }
+       }
+
+       cmd->dirty.out_nr_dirty = count;
+       rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+out_free:
+       kvfree(tmp);
+out_put:
+       iommufd_put_object(&hwpt->obj);
+       return rc;
+}
+
 void iommufd_selftest_destroy(struct iommufd_object *obj)
 {
        struct selftest_obj *sobj = container_of(obj, struct selftest_obj, obj);
@@ -986,6 +1260,7 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
                                                 cmd->add_reserved.start,
                                                 cmd->add_reserved.length);
        case IOMMU_TEST_OP_MOCK_DOMAIN:
+       case IOMMU_TEST_OP_MOCK_DOMAIN_FLAGS:
                return iommufd_test_mock_domain(ucmd, cmd);
        case IOMMU_TEST_OP_MOCK_DOMAIN_REPLACE:
                return iommufd_test_mock_domain_replace(
@@ -1027,6 +1302,12 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
                        return -EINVAL;
                iommufd_test_memory_limit = cmd->memory_limit.limit;
                return 0;
+       case IOMMU_TEST_OP_DIRTY:
+               return iommufd_test_dirty(ucmd, cmd->id, cmd->dirty.iova,
+                                         cmd->dirty.length,
+                                         cmd->dirty.page_size,
+                                         u64_to_user_ptr(cmd->dirty.uptr),
+                                         cmd->dirty.flags);
        default:
                return -EOPNOTSUPP;
        }