123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (C) 2022 Google LLC
- * Author: David Brazdil <[email protected]>
- */
- #include <linux/kvm_host.h>
- #include <asm/kvm_asm.h>
- #include <asm/kvm_hyp.h>
- #include <asm/kvm_mmu.h>
- #include <asm/kvm_pkvm.h>
- #include <hyp/adjust_pc.h>
- #include <nvhe/iommu.h>
- #include <nvhe/mm.h>
- #include <nvhe/modules.h>
- #include <nvhe/pkvm.h>
- #define DRV_ID(drv_addr) ((unsigned long)drv_addr)
- enum {
- IOMMU_DRIVER_NOT_READY = 0,
- IOMMU_DRIVER_INITIALIZING,
- IOMMU_DRIVER_READY,
- };
- /* List of registered IOMMU drivers, protected with iommu_drv_lock. */
- static LIST_HEAD(iommu_drivers);
- /* IOMMU device list. Must only be accessed with host_mmu.lock held. */
- static LIST_HEAD(iommu_list);
- static bool iommu_finalized;
- static DEFINE_HYP_SPINLOCK(iommu_registration_lock);
- static DEFINE_HYP_SPINLOCK(iommu_drv_lock);
- static void *iommu_mem_pool;
- static size_t iommu_mem_remaining;
- static void assert_host_component_locked(void)
- {
- hyp_assert_lock_held(&host_mmu.lock);
- }
- static void host_lock_component(void)
- {
- hyp_spin_lock(&host_mmu.lock);
- }
- static void host_unlock_component(void)
- {
- hyp_spin_unlock(&host_mmu.lock);
- }
- /*
- * Find IOMMU driver by its ID. The input ID is treated as unstrusted
- * and is properly validated.
- */
- static inline struct pkvm_iommu_driver *get_driver(unsigned long id)
- {
- struct pkvm_iommu_driver *drv, *ret = NULL;
- hyp_spin_lock(&iommu_drv_lock);
- list_for_each_entry(drv, &iommu_drivers, list) {
- if (DRV_ID(drv) == id) {
- ret = drv;
- break;
- }
- }
- hyp_spin_unlock(&iommu_drv_lock);
- return ret;
- }
- static inline bool driver_acquire_init(struct pkvm_iommu_driver *drv)
- {
- return atomic_cmpxchg_acquire(&drv->state, IOMMU_DRIVER_NOT_READY,
- IOMMU_DRIVER_INITIALIZING)
- == IOMMU_DRIVER_NOT_READY;
- }
- static inline void driver_release_init(struct pkvm_iommu_driver *drv,
- bool success)
- {
- atomic_set_release(&drv->state, success ? IOMMU_DRIVER_READY
- : IOMMU_DRIVER_NOT_READY);
- }
- static inline bool is_driver_ready(struct pkvm_iommu_driver *drv)
- {
- return atomic_read(&drv->state) == IOMMU_DRIVER_READY;
- }
- static size_t __iommu_alloc_size(struct pkvm_iommu_driver *drv)
- {
- return ALIGN(sizeof(struct pkvm_iommu) + drv->ops->data_size,
- sizeof(unsigned long));
- }
- static bool validate_driver_id_unique(struct pkvm_iommu_driver *drv)
- {
- struct pkvm_iommu_driver *cur;
- hyp_assert_lock_held(&iommu_drv_lock);
- list_for_each_entry(cur, &iommu_drivers, list) {
- if (DRV_ID(drv) == DRV_ID(cur))
- return false;
- }
- return true;
- }
- static int __pkvm_register_iommu_driver(struct pkvm_iommu_driver *drv)
- {
- int ret = 0;
- if (!drv)
- return -EINVAL;
- hyp_assert_lock_held(&iommu_registration_lock);
- hyp_spin_lock(&iommu_drv_lock);
- if (validate_driver_id_unique(drv))
- list_add_tail(&drv->list, &iommu_drivers);
- else
- ret = -EEXIST;
- hyp_spin_unlock(&iommu_drv_lock);
- return ret;
- }
- /* Global memory pool for allocating IOMMU list entry structs. */
- static inline struct pkvm_iommu *alloc_iommu(struct pkvm_iommu_driver *drv,
- void *mem, size_t mem_size)
- {
- size_t size = __iommu_alloc_size(drv);
- void *ptr;
- assert_host_component_locked();
- /*
- * If new memory is being provided, replace the existing pool with it.
- * Any remaining memory in the pool is discarded.
- */
- if (mem && mem_size) {
- iommu_mem_pool = mem;
- iommu_mem_remaining = mem_size;
- }
- if (size > iommu_mem_remaining)
- return NULL;
- ptr = iommu_mem_pool;
- iommu_mem_pool += size;
- iommu_mem_remaining -= size;
- return ptr;
- }
- static inline void free_iommu(struct pkvm_iommu_driver *drv, struct pkvm_iommu *ptr)
- {
- size_t size = __iommu_alloc_size(drv);
- assert_host_component_locked();
- if (!ptr)
- return;
- /* Only allow freeing the last allocated buffer. */
- if ((void *)ptr + size != iommu_mem_pool)
- return;
- iommu_mem_pool -= size;
- iommu_mem_remaining += size;
- }
- static bool is_overlap(phys_addr_t r1_start, size_t r1_size,
- phys_addr_t r2_start, size_t r2_size)
- {
- phys_addr_t r1_end = r1_start + r1_size;
- phys_addr_t r2_end = r2_start + r2_size;
- return (r1_start < r2_end) && (r2_start < r1_end);
- }
- static bool is_mmio_range(phys_addr_t base, size_t size)
- {
- struct memblock_region *reg;
- phys_addr_t limit = BIT(host_mmu.pgt.ia_bits);
- size_t i;
- /* Check against limits of host IPA space. */
- if ((base >= limit) || !size || (size > limit - base))
- return false;
- for (i = 0; i < hyp_memblock_nr; i++) {
- reg = &hyp_memory[i];
- if (is_overlap(base, size, reg->base, reg->size))
- return false;
- }
- return true;
- }
- static int __snapshot_host_stage2(u64 start, u64 pa_max, u32 level,
- kvm_pte_t *ptep,
- enum kvm_pgtable_walk_flags flags,
- void * const arg)
- {
- struct pkvm_iommu_driver * const drv = arg;
- u64 end = start + kvm_granule_size(level);
- kvm_pte_t pte = *ptep;
- /*
- * Valid stage-2 entries are created lazily, invalid ones eagerly.
- * Note: In the future we may need to check if [start,end) is MMIO.
- * Note: Drivers initialize their PTs to all memory owned by the host,
- * so we only call the driver on regions where that is not the case.
- */
- if (pte && !kvm_pte_valid(pte))
- drv->ops->host_stage2_idmap_prepare(start, end, /*prot*/ 0);
- return 0;
- }
- static int snapshot_host_stage2(struct pkvm_iommu_driver * const drv)
- {
- struct kvm_pgtable_walker walker = {
- .cb = __snapshot_host_stage2,
- .arg = drv,
- .flags = KVM_PGTABLE_WALK_LEAF,
- };
- struct kvm_pgtable *pgt = &host_mmu.pgt;
- if (!drv->ops->host_stage2_idmap_prepare)
- return 0;
- return kvm_pgtable_walk(pgt, 0, BIT(pgt->ia_bits), &walker);
- }
- static bool validate_against_existing_iommus(struct pkvm_iommu *dev)
- {
- struct pkvm_iommu *other;
- assert_host_component_locked();
- list_for_each_entry(other, &iommu_list, list) {
- /* Device ID must be unique. */
- if (dev->id == other->id)
- return false;
- /* MMIO regions must not overlap. */
- if (is_overlap(dev->pa, dev->size, other->pa, other->size))
- return false;
- }
- return true;
- }
- static struct pkvm_iommu *find_iommu_by_id(unsigned long id)
- {
- struct pkvm_iommu *dev;
- assert_host_component_locked();
- list_for_each_entry(dev, &iommu_list, list) {
- if (dev->id == id)
- return dev;
- }
- return NULL;
- }
- /*
- * Initialize EL2 IOMMU driver.
- *
- * This is a common hypercall for driver initialization. Driver-specific
- * arguments are passed in a shared memory buffer. The driver is expected to
- * initialize it's page-table bookkeeping.
- */
- int __pkvm_iommu_driver_init(struct pkvm_iommu_driver *drv, void *data, size_t size)
- {
- const struct pkvm_iommu_ops *ops;
- int ret = 0;
- /* New driver initialization not allowed after __pkvm_iommu_finalize(). */
- hyp_spin_lock(&iommu_registration_lock);
- if (iommu_finalized) {
- ret = -EPERM;
- goto out_unlock;
- }
- ret = __pkvm_register_iommu_driver(drv);
- if (ret)
- return ret;
- if (!drv->ops) {
- ret = -EINVAL;
- goto out_unlock;
- }
- if (!driver_acquire_init(drv)) {
- ret = -EBUSY;
- goto out_unlock;
- }
- ops = drv->ops;
- /* This can change stage-2 mappings. */
- if (ops->init) {
- ret = hyp_pin_shared_mem(data, data + size);
- if (!ret) {
- ret = ops->init(data, size);
- hyp_unpin_shared_mem(data, data + size);
- }
- if (ret)
- goto out_release;
- }
- /*
- * Walk host stage-2 and pass current mappings to the driver. Start
- * accepting host stage-2 updates as soon as the host lock is released.
- */
- host_lock_component();
- ret = snapshot_host_stage2(drv);
- if (!ret)
- driver_release_init(drv, /*success=*/true);
- host_unlock_component();
- out_release:
- if (ret)
- driver_release_init(drv, /*success=*/false);
- out_unlock:
- hyp_spin_unlock(&iommu_registration_lock);
- return ret;
- }
- int __pkvm_iommu_register(unsigned long dev_id, unsigned long drv_id,
- phys_addr_t dev_pa, size_t dev_size,
- unsigned long parent_id, u8 flags,
- void *kern_mem_va)
- {
- struct pkvm_iommu *dev = NULL;
- struct pkvm_iommu_driver *drv;
- void *mem_va = NULL;
- int ret = 0;
- /* New device registration not allowed after __pkvm_iommu_finalize(). */
- hyp_spin_lock(&iommu_registration_lock);
- if (iommu_finalized) {
- ret = -EPERM;
- goto out_unlock;
- }
- drv = get_driver(drv_id);
- if (!drv || !is_driver_ready(drv)) {
- ret = -ENOENT;
- goto out_unlock;
- }
- if (!PAGE_ALIGNED(dev_pa) || !PAGE_ALIGNED(dev_size)) {
- ret = -EINVAL;
- goto out_unlock;
- }
- if (!is_mmio_range(dev_pa, dev_size)) {
- ret = -EINVAL;
- goto out_unlock;
- }
- /*
- * Accept memory donation if the host is providing new memory.
- * Note: We do not return the memory even if there is an error later.
- */
- if (kern_mem_va) {
- mem_va = kern_hyp_va(kern_mem_va);
- if (!PAGE_ALIGNED(mem_va)) {
- ret = -EINVAL;
- goto out_unlock;
- }
- ret = __pkvm_host_donate_hyp(hyp_virt_to_pfn(mem_va), 1);
- if (ret)
- goto out_unlock;
- }
- host_lock_component();
- /* Allocate memory for the new device entry. */
- dev = alloc_iommu(drv, mem_va, PAGE_SIZE);
- if (!dev) {
- ret = -ENOMEM;
- goto out_free;
- }
- /* Populate the new device entry. */
- *dev = (struct pkvm_iommu){
- .children = LIST_HEAD_INIT(dev->children),
- .id = dev_id,
- .ops = drv->ops,
- .pa = dev_pa,
- .va = hyp_phys_to_virt(dev_pa),
- .size = dev_size,
- .flags = flags,
- };
- if (!validate_against_existing_iommus(dev)) {
- ret = -EBUSY;
- goto out_free;
- }
- if (parent_id) {
- dev->parent = find_iommu_by_id(parent_id);
- if (!dev->parent) {
- ret = -EINVAL;
- goto out_free;
- }
- if (dev->parent->ops->validate_child) {
- ret = dev->parent->ops->validate_child(dev->parent, dev);
- if (ret)
- goto out_free;
- }
- }
- if (dev->ops->validate) {
- ret = dev->ops->validate(dev);
- if (ret)
- goto out_free;
- }
- ret = __pkvm_host_donate_hyp_locked(hyp_phys_to_pfn(dev_pa),
- PAGE_ALIGN(dev_size) >> PAGE_SHIFT);
- if (ret)
- goto out_free;
- /* Register device and prevent host from mapping the MMIO range. */
- list_add_tail(&dev->list, &iommu_list);
- if (dev->parent)
- list_add_tail(&dev->siblings, &dev->parent->children);
- out_free:
- if (ret)
- free_iommu(drv, dev);
- host_unlock_component();
- out_unlock:
- hyp_spin_unlock(&iommu_registration_lock);
- return ret;
- }
- int __pkvm_iommu_finalize(int err)
- {
- int ret = 0;
- /* Err is not currently used in EL2.*/
- WARN_ON(err);
- hyp_spin_lock(&iommu_registration_lock);
- if (!iommu_finalized)
- iommu_finalized = true;
- else
- ret = -EPERM;
- hyp_spin_unlock(&iommu_registration_lock);
- return ret;
- }
- int __pkvm_iommu_pm_notify(unsigned long dev_id, enum pkvm_iommu_pm_event event)
- {
- struct pkvm_iommu *dev;
- int ret;
- host_lock_component();
- dev = find_iommu_by_id(dev_id);
- if (dev) {
- if (event == PKVM_IOMMU_PM_SUSPEND) {
- ret = dev->ops->suspend ? dev->ops->suspend(dev) : 0;
- if (!ret)
- dev->powered = false;
- } else if (event == PKVM_IOMMU_PM_RESUME) {
- ret = dev->ops->resume ? dev->ops->resume(dev) : 0;
- if (!ret)
- dev->powered = true;
- } else {
- ret = -EINVAL;
- }
- } else {
- ret = -ENODEV;
- }
- host_unlock_component();
- return ret;
- }
- bool pkvm_iommu_host_dabt_handler(struct kvm_cpu_context *host_ctxt, u32 esr,
- phys_addr_t pa)
- {
- struct pkvm_iommu *dev;
- assert_host_component_locked();
- list_for_each_entry(dev, &iommu_list, list) {
- if (pa < dev->pa || pa >= dev->pa + dev->size)
- continue;
- /* No 'powered' check - the host assumes it is powered. */
- if (!dev->ops->host_dabt_handler ||
- !dev->ops->host_dabt_handler(dev, host_ctxt, esr, pa - dev->pa))
- return false;
- kvm_skip_host_instr();
- return true;
- }
- return false;
- }
- void pkvm_iommu_host_stage2_idmap(phys_addr_t start, phys_addr_t end,
- enum kvm_pgtable_prot prot)
- {
- struct pkvm_iommu_driver *drv;
- struct pkvm_iommu *dev;
- assert_host_component_locked();
- hyp_spin_lock(&iommu_drv_lock);
- list_for_each_entry(drv, &iommu_drivers, list) {
- if (drv && is_driver_ready(drv) && drv->ops->host_stage2_idmap_prepare)
- drv->ops->host_stage2_idmap_prepare(start, end, prot);
- }
- hyp_spin_unlock(&iommu_drv_lock);
- list_for_each_entry(dev, &iommu_list, list) {
- if (dev->powered && dev->ops->host_stage2_idmap_apply)
- dev->ops->host_stage2_idmap_apply(dev, start, end);
- }
- list_for_each_entry(dev, &iommu_list, list) {
- if (dev->powered && dev->ops->host_stage2_idmap_complete)
- dev->ops->host_stage2_idmap_complete(dev);
- }
- }
|