// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2017-2018 Hisilicon Limited. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ACPI #include #endif #define SVM_DEVICE_NAME "svm" #ifndef CONFIG_ACPI static int probe_index; #endif static DECLARE_RWSEM(svm_sem); struct core_device { struct device dev; struct iommu_group *group; struct iommu_domain *domain; bool smmu_bypass; }; struct svm_device { unsigned long long id; struct miscdevice miscdev; struct device *dev; phys_addr_t l2buff; unsigned long l2size; }; struct svm_bind_process { pid_t vpid; u64 ttbr; u64 tcr; int pasid; u32 flags; #define SVM_BIND_PID (1 << 0) }; struct svm_process { struct pid *pid; struct mm_struct *mm; unsigned long asid; struct kref kref; struct rb_node rb_node; struct mmu_notifier notifier; /* For postponed release */ struct rcu_head rcu; struct list_head contexts; int pasid; struct mutex mutex; struct rb_root sdma_list; }; /* keep the relationship of svm_process and svm_device */ struct svm_context { struct svm_process *process; struct svm_device *sdev; struct list_head process_head; atomic_t ref; }; static struct bus_type svm_bus_type = { .name = "svm_bus", }; static int svm_open(struct inode *inode, struct file *file) { return 0; } static int svm_remove_core(struct device *dev, void *data) { /*TODO*/ return 0; } #ifdef CONFIG_ACPI static int svm_acpi_add_core(struct svm_device *sdev, struct acpi_device *children, int id) { /*TODO*/ return 0; } static int svm_init_core(struct svm_device *sdev) { int err = 0; struct device *dev = sdev->dev; struct acpi_device *adev = ACPI_COMPANION(sdev->dev); struct acpi_device *cdev = NULL; int id = 0; down_write(&svm_sem); if (!svm_bus_type.iommu_ops) { err = bus_register(&svm_bus_type); if (err) { up_write(&svm_sem); dev_err(dev, "failed to register svm_bus_type\n"); return err; } err = bus_set_iommu(&svm_bus_type, dev->bus->iommu_ops); if (err) { up_write(&svm_sem); dev_err(dev, "failed to set iommu for svm_bus_type\n"); goto err_unregister_bus; } } else if (svm_bus_type.iommu_ops != dev->bus->iommu_ops) { err = -EBUSY; up_write(&svm_sem); dev_err(dev, "iommu_ops configured, but changed!\n"); goto err_unregister_bus; } up_write(&svm_sem); list_for_each_entry(cdev, &adev->children, node) { err = svm_acpi_add_core(sdev, cdev, id++); if (err) device_for_each_child(dev, NULL, svm_remove_core); } return err; err_unregister_bus: bus_unregister(&svm_bus_type); return err; } #else static int svm_of_add_core(struct svm_device *sdev, struct device_node *np) { /*TODO*/ return 0; } static int svm_init_core(struct svm_device *sdev, struct device_node *np) { int err = 0; struct device_node *child = NULL; struct device *dev = sdev->dev; down_write(&svm_sem); if (svm_bus_type.iommu_ops == NULL) { err = bus_register(&svm_bus_type); if (err) { up_write(&svm_sem); dev_err(dev, "failed to register svm_bus_type\n"); return err; } err = bus_set_iommu(&svm_bus_type, dev->bus->iommu_ops); if (err) { up_write(&svm_sem); dev_err(dev, "failed to set iommu for svm_bus_type\n"); goto err_unregister_bus; } } else if (svm_bus_type.iommu_ops != dev->bus->iommu_ops) { err = -EBUSY; up_write(&svm_sem); dev_err(dev, "iommu_ops configured, but changed!\n"); goto err_unregister_bus; } up_write(&svm_sem); for_each_available_child_of_node(np, child) { err = svm_of_add_core(sdev, child); if (err) device_for_each_child(dev, NULL, svm_remove_core); } return err; err_unregister_bus: bus_unregister(&svm_bus_type); return err; } #endif /*svm ioctl will include some case for HI1980 and HI1910*/ static long svm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { /*TODO add svm ioctl*/ return 0; } static const struct file_operations svm_fops = { .owner = THIS_MODULE, .open = svm_open, .unlocked_ioctl = svm_ioctl, }; /*svm device probe this is init the svm device*/ static int svm_device_probe(struct platform_device *pdev) { int err = -1; struct device *dev = &pdev->dev; struct svm_device *sdev = NULL; #ifndef CONFIG_ACPI struct device_node *np = dev->of_node; int alias_id; if (np == NULL) return -ENODEV; #endif if (!dev->bus->iommu_ops) { dev_dbg(dev, "defer probe svm device\n"); return -EPROBE_DEFER; } sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); if (sdev == NULL) return -ENOMEM; #ifdef CONFIG_ACPI err = device_property_read_u64(dev, "svmid", &sdev->id); if (err) { dev_err(dev, "failed to get this svm device id\n"); return err; } #else alias_id = of_alias_get_id(np, "svm"); if (alias_id < 0) sdev->id = probe_index; else sdev->id = alias_id; #endif sdev->dev = dev; sdev->miscdev.minor = MISC_DYNAMIC_MINOR; sdev->miscdev.fops = &svm_fops; sdev->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, SVM_DEVICE_NAME"%llu", sdev->id); if (sdev->miscdev.name == NULL) err = -ENOMEM; dev_set_drvdata(dev, sdev); err = misc_register(&sdev->miscdev); if (err) { dev_err(dev, "Unable to register misc device\n"); return err; } #ifdef CONFIG_ACPI err = svm_init_core(sdev); #else err = svm_init_core(sdev, np); #endif if (err) { dev_err(dev, "failed to init cores\n"); goto err_unregister_misc; } #ifndef CONFIG_ACPI probe_index++; #endif return err; err_unregister_misc: misc_deregister(&sdev->miscdev); return err; } /*svm device remove this is device remove*/ static int svm_device_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct svm_device *sdev = dev_get_drvdata(dev); misc_deregister(&sdev->miscdev); return 0; } #ifdef CONFIG_ACPI static const struct acpi_device_id svm_acpi_match[] = { { "HSVM1980", 0}, { } }; MODULE_DEVICE_TABLE(acpi, svm_acpi_match); #else static const struct of_device_id svm_of_match[] = { { .compatible = "hisilicon,svm" }, { } }; MODULE_DEVICE_TABLE(of, svm_of_match); #endif /*svm acpi probe and remove*/ static struct platform_driver svm_driver = { .probe = svm_device_probe, .remove = svm_device_remove, .driver = { .name = SVM_DEVICE_NAME, #ifdef CONFIG_ACPI .acpi_match_table = ACPI_PTR(svm_acpi_match), #else .of_match_table = svm_of_match, #endif }, }; module_platform_driver(svm_driver); MODULE_DESCRIPTION("Hisilicon SVM driver"); MODULE_AUTHOR("JianKang Chen "); MODULE_LICENSE("GPL v2");