Manage MSI iommu pages.
This allows the interrupt controller driver only need a small change to create a map for the page the device will write to raise an interrupt. Submitted by: andrew Reviewed by: kib Sponsored by: Innovate DSbD Differential Revision: https://reviews.freebsd.org/D26705
This commit is contained in:
parent
956cc8e1b9
commit
e707c8be4e
sys
@ -111,6 +111,9 @@ struct iommu_domain {
|
||||
iommu_gaddr_t end; /* (c) Highest address + 1 in
|
||||
the guest AS */
|
||||
struct iommu_map_entry *first_place, *last_place; /* (d) */
|
||||
struct iommu_map_entry *msi_entry; /* (d) Arch-specific */
|
||||
iommu_gaddr_t msi_base; /* (d) Arch-specific */
|
||||
vm_paddr_t msi_phys; /* (d) Arch-specific */
|
||||
u_int flags; /* (u) */
|
||||
};
|
||||
|
||||
|
@ -63,6 +63,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <dev/pci/pcivar.h>
|
||||
#include <dev/iommu/iommu.h>
|
||||
#include <dev/iommu/iommu_gas.h>
|
||||
#include <dev/iommu/iommu_msi.h>
|
||||
#include <machine/atomic.h>
|
||||
#include <machine/bus.h>
|
||||
#include <machine/md_var.h>
|
||||
@ -724,6 +725,69 @@ iommu_map(struct iommu_domain *domain,
|
||||
return (error);
|
||||
}
|
||||
|
||||
int
|
||||
iommu_map_msi(struct iommu_ctx *ctx, iommu_gaddr_t size, int offset,
|
||||
u_int eflags, u_int flags, vm_page_t *ma)
|
||||
{
|
||||
struct iommu_domain *domain;
|
||||
struct iommu_map_entry *entry;
|
||||
int error;
|
||||
|
||||
error = 0;
|
||||
domain = ctx->domain;
|
||||
|
||||
/* Check if there is already an MSI page allocated */
|
||||
IOMMU_DOMAIN_LOCK(domain);
|
||||
entry = domain->msi_entry;
|
||||
IOMMU_DOMAIN_UNLOCK(domain);
|
||||
|
||||
if (entry == NULL) {
|
||||
error = iommu_gas_map(domain, &ctx->tag->common, size, offset,
|
||||
eflags, flags, ma, &entry);
|
||||
IOMMU_DOMAIN_LOCK(domain);
|
||||
if (error == 0) {
|
||||
if (domain->msi_entry == NULL) {
|
||||
MPASS(domain->msi_base == 0);
|
||||
MPASS(domain->msi_phys == 0);
|
||||
|
||||
domain->msi_entry = entry;
|
||||
domain->msi_base = entry->start;
|
||||
domain->msi_phys = VM_PAGE_TO_PHYS(ma[0]);
|
||||
} else {
|
||||
/*
|
||||
* We lost the race and already have an
|
||||
* MSI page allocated. Free the unneeded entry.
|
||||
*/
|
||||
iommu_gas_free_entry(domain, entry);
|
||||
}
|
||||
} else if (domain->msi_entry != NULL) {
|
||||
/*
|
||||
* The allocation failed, but another succeeded.
|
||||
* Return success as there is a valid MSI page.
|
||||
*/
|
||||
error = 0;
|
||||
}
|
||||
IOMMU_DOMAIN_UNLOCK(domain);
|
||||
}
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
void
|
||||
iommu_translate_msi(struct iommu_domain *domain, uint64_t *addr)
|
||||
{
|
||||
|
||||
*addr = (*addr - domain->msi_phys) + domain->msi_base;
|
||||
|
||||
KASSERT(*addr >= domain->msi_entry->start,
|
||||
("%s: Address is below the MSI entry start address (%jx < %jx)",
|
||||
__func__, (uintmax_t)*addr, (uintmax_t)domain->msi_entry->start));
|
||||
|
||||
KASSERT(*addr + sizeof(*addr) <= domain->msi_entry->end,
|
||||
("%s: Address is above the MSI entry end address (%jx < %jx)",
|
||||
__func__, (uintmax_t)*addr, (uintmax_t)domain->msi_entry->end));
|
||||
}
|
||||
|
||||
int
|
||||
iommu_map_region(struct iommu_domain *domain, struct iommu_map_entry *entry,
|
||||
u_int eflags, u_int flags, vm_page_t *ma)
|
||||
|
46
sys/dev/iommu/iommu_msi.h
Normal file
46
sys/dev/iommu/iommu_msi.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2020 Andrew Turner
|
||||
*
|
||||
* This work was supported by Innovate UK project 105694, "Digital Security
|
||||
* by Design (DSbD) Technology Platform Prototype".
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef _DEV_IOMMU_IOMMU_MSI_H_
|
||||
#define _DEV_IOMMU_IOMMU_MSI_H_
|
||||
|
||||
#include <dev/iommu/iommu_types.h>
|
||||
|
||||
struct iommu_unit;
|
||||
struct iommu_domain;
|
||||
struct iommu_ctx;
|
||||
|
||||
void iommu_translate_msi(struct iommu_domain *domain, uint64_t *addr);
|
||||
int iommu_map_msi(struct iommu_ctx *ctx, iommu_gaddr_t size, int offset,
|
||||
u_int eflags, u_int flags, vm_page_t *ma);
|
||||
|
||||
#endif /* !_DEV_IOMMU_IOMMU_MSI_H_ */
|
@ -32,6 +32,9 @@
|
||||
INTERFACE msi;
|
||||
|
||||
HEADER {
|
||||
#include <machine/bus.h>
|
||||
#include <dev/iommu/iommu_msi.h>
|
||||
|
||||
struct intr_irqsrc;
|
||||
};
|
||||
|
||||
@ -72,3 +75,8 @@ METHOD int map_msi {
|
||||
uint32_t *data;
|
||||
};
|
||||
|
||||
METHOD int iommu_init {
|
||||
device_t dev;
|
||||
device_t child;
|
||||
struct iommu_domain **domain;
|
||||
};
|
||||
|
@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
#include "opt_ddb.h"
|
||||
#include "opt_hwpmc_hooks.h"
|
||||
#include "opt_iommu.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
@ -50,6 +51,8 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/queue.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/interrupt.h>
|
||||
#include <sys/taskqueue.h>
|
||||
#include <sys/tree.h>
|
||||
#include <sys/conf.h>
|
||||
#include <sys/cpuset.h>
|
||||
#include <sys/rman.h>
|
||||
@ -70,6 +73,10 @@ __FBSDID("$FreeBSD$");
|
||||
#include <ddb/ddb.h>
|
||||
#endif
|
||||
|
||||
#ifdef IOMMU
|
||||
#include <dev/iommu/iommu_msi.h>
|
||||
#endif
|
||||
|
||||
#include "pic_if.h"
|
||||
#include "msi_if.h"
|
||||
|
||||
@ -1290,6 +1297,9 @@ int
|
||||
intr_alloc_msi(device_t pci, device_t child, intptr_t xref, int count,
|
||||
int maxcount, int *irqs)
|
||||
{
|
||||
#ifdef IOMMU
|
||||
struct iommu_domain *domain;
|
||||
#endif
|
||||
struct intr_irqsrc **isrc;
|
||||
struct intr_pic *pic;
|
||||
device_t pdev;
|
||||
@ -1304,6 +1314,16 @@ intr_alloc_msi(device_t pci, device_t child, intptr_t xref, int count,
|
||||
("%s: Found a non-MSI controller: %s", __func__,
|
||||
device_get_name(pic->pic_dev)));
|
||||
|
||||
#ifdef IOMMU
|
||||
/*
|
||||
* If this is the first time we have used this context ask the
|
||||
* interrupt controller to map memory the msi source will need.
|
||||
*/
|
||||
err = MSI_IOMMU_INIT(pic->pic_dev, child, &domain);
|
||||
if (err != 0)
|
||||
return (err);
|
||||
#endif
|
||||
|
||||
isrc = malloc(sizeof(*isrc) * count, M_INTRNG, M_WAITOK);
|
||||
err = MSI_ALLOC_MSI(pic->pic_dev, child, count, maxcount, &pdev, isrc);
|
||||
if (err != 0) {
|
||||
@ -1312,9 +1332,13 @@ intr_alloc_msi(device_t pci, device_t child, intptr_t xref, int count,
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
#ifdef IOMMU
|
||||
isrc[i]->isrc_iommu = domain;
|
||||
#endif
|
||||
msi = (struct intr_map_data_msi *)intr_alloc_map_data(
|
||||
INTR_MAP_DATA_MSI, sizeof(*msi), M_WAITOK | M_ZERO);
|
||||
msi-> isrc = isrc[i];
|
||||
|
||||
irqs[i] = intr_map_irq(pic->pic_dev, xref,
|
||||
(struct intr_map_data *)msi);
|
||||
}
|
||||
@ -1365,6 +1389,9 @@ intr_release_msi(device_t pci, device_t child, intptr_t xref, int count,
|
||||
int
|
||||
intr_alloc_msix(device_t pci, device_t child, intptr_t xref, int *irq)
|
||||
{
|
||||
#ifdef IOMMU
|
||||
struct iommu_domain *domain;
|
||||
#endif
|
||||
struct intr_irqsrc *isrc;
|
||||
struct intr_pic *pic;
|
||||
device_t pdev;
|
||||
@ -1379,10 +1406,23 @@ intr_alloc_msix(device_t pci, device_t child, intptr_t xref, int *irq)
|
||||
("%s: Found a non-MSI controller: %s", __func__,
|
||||
device_get_name(pic->pic_dev)));
|
||||
|
||||
#ifdef IOMMU
|
||||
/*
|
||||
* If this is the first time we have used this context ask the
|
||||
* interrupt controller to map memory the msi source will need.
|
||||
*/
|
||||
err = MSI_IOMMU_INIT(pic->pic_dev, child, &domain);
|
||||
if (err != 0)
|
||||
return (err);
|
||||
#endif
|
||||
|
||||
err = MSI_ALLOC_MSIX(pic->pic_dev, child, &pdev, &isrc);
|
||||
if (err != 0)
|
||||
return (err);
|
||||
|
||||
#ifdef IOMMU
|
||||
isrc->isrc_iommu = domain;
|
||||
#endif
|
||||
msi = (struct intr_map_data_msi *)intr_alloc_map_data(
|
||||
INTR_MAP_DATA_MSI, sizeof(*msi), M_WAITOK | M_ZERO);
|
||||
msi->isrc = isrc;
|
||||
@ -1444,6 +1484,12 @@ intr_map_msi(device_t pci, device_t child, intptr_t xref, int irq,
|
||||
return (EINVAL);
|
||||
|
||||
err = MSI_MAP_MSI(pic->pic_dev, child, isrc, addr, data);
|
||||
|
||||
#ifdef IOMMU
|
||||
if (isrc->isrc_iommu != NULL)
|
||||
iommu_translate_msi(isrc->isrc_iommu, addr);
|
||||
#endif
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,8 @@ struct intr_irqsrc {
|
||||
intr_irq_filter_t * isrc_filter;
|
||||
void * isrc_arg;
|
||||
#endif
|
||||
/* Used by MSI interrupts to store the iommu details */
|
||||
void * isrc_iommu;
|
||||
};
|
||||
|
||||
/* Intr interface for PIC. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user