2026-01-29 22:25:33 +08:00

1781 lines
41 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* iommu.c: Generic sw64 IOMMU support
*
* This is designed and tested for 3231. If there are no changes in hardware
* in later chips, then it should work just as well.
*
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/gfp.h>
#include <linux/export.h>
#include <linux/scatterlist.h>
#include <linux/log2.h>
#include <linux/dma-mapping.h>
#include <linux/dma-map-ops.h>
#include <linux/dma-direct.h>
#include <linux/iommu.h>
#include <linux/iommu-helper.h>
#include <linux/iova.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/syscore_ops.h>
#include <linux/swiotlb.h>
#include <linux/cache.h>
#include <linux/module.h>
#include <asm/dma.h>
#include <linux/io.h>
#include <asm/sw64io.h>
#include <asm/hw_init.h>
#include "sunway_iommu.h"
#define MAX_DOMAIN_NUM 65536
#define IOVA_PFN(addr) ((addr) >> PAGE_SHIFT)
#define SW64_32BIT_DMA_LIMIT (0xe0000000 - 1)
#define SW64_64BIT_DMA_LIMIT ((1UL << 41) - 1)
#define SW64_BAR_ADDRESS (IO_BASE | PCI_BASE)
#define SW64_IOMMU_PGSIZES (((1ULL) << PAGE_SHIFT) \
| ((1ULL) << PAGE_8M_SHIFT) \
| ((1ULL) << PAGE_512M_SHIFT) \
| ((1ULL) << PAGE_8G_SHIFT))
#define IDENTMAP_ALL ((1U) << 0)
#define DMA_MASK64 ((1U) << 1)
#define PTE_VALID 0x8000000000000000UL
#define LAST_STAGE 0x100UL
#define PTE_GRN_8M 0x10UL
#define PTE_GRN_512M 0x20UL
#define PTE_GRN_8G 0x30UL
#define PTE_WRITEE 0x2UL
#define PTE_READE 0x1UL
#define PTE_RWE 0x3UL
#define PTE_FLAGS_MASK 0x8000000000000133UL
#define PAGE_8G_OFFSET_MASK ((1UL << PAGE_8G_SHIFT) - 1)
#define PAGE_512M_OFFSET_MASK ((1UL << PAGE_512M_SHIFT) - 1)
#define PAGE_8M_OFFSET_MASK ((1UL << PAGE_8M_SHIFT) - 1)
/* IOMMU Exceptional Status */
enum exceptype {
DTE_LEVEL1 = 0x0,
DTE_LEVEL2,
PTE_LEVEL1,
PTE_LEVEL2,
PTE_LEVEL3,
UNAUTHORIZED_ACCESS,
ILLEGAL_RESPONSE,
DTE_LEVEL1_VAL,
DTE_LEVEL2_VAL,
PTE_LEVEL1_VAL,
PTE_LEVEL2_VAL,
PTE_LEVEL3_VAL,
};
u64 iommu_enable_cmd; /* default IOMMU boot param: 0 */
unsigned long *sunway_iommu_domain_bitmap;
static DEFINE_SPINLOCK(domain_bitmap_lock);
static DEFINE_SPINLOCK(sunway_iommu_device_table_lock);
spinlock_t sunway_domain_lock;
static LLIST_HEAD(dev_data_list);
LIST_HEAD(sunway_domain_list);
struct dma_domain {
struct sunway_iommu_domain sdomain;
struct iova_domain iovad;
};
const struct iommu_ops sunway_iommu_ops;
static const struct dma_map_ops sunway_dma_ops;
/* flush helpers */
static void piu_flush_all(struct pci_controller *hose)
{
write_piu_ior0(hose->node, hose->index, DTLB_FLUSHALL, 0);
write_piu_ior0(hose->node, hose->index, PTLB_FLUSHALL, 0);
write_piu_ior0(hose->node, hose->index, PCACHE_FLUSHALL, 0);
}
void flush_pcache_by_addr(struct sunway_iommu_domain *sdomain, unsigned long flush_addr)
{
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
list_for_each_entry(sdev, &sdomain->dev_list, list) {
hose = pci_bus_to_pci_controller(sdev->pdev->bus);
flush_addr = __pa(flush_addr);
/* Set memory bar here */
mb();
write_piu_ior0(hose->node, hose->index,
PCACHE_FLUSHPADDR, flush_addr);
}
}
void flush_ptlb_by_addr(struct sunway_iommu_domain *sdomain, unsigned long flush_addr)
{
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
struct pci_dev *pdev;
list_for_each_entry(sdev, &sdomain->dev_list, list) {
pdev = sdev->pdev;
hose = pci_bus_to_pci_controller(pdev->bus);
flush_addr = (pdev->bus->number << 8)
| pdev->devfn | (flush_addr << 16);
write_piu_ior0(hose->node, hose->index,
PTLB_FLUSHVADDR, flush_addr);
}
}
/* domain helpers */
static struct sunway_iommu_domain *to_sunway_domain(struct iommu_domain *dom)
{
return container_of(dom, struct sunway_iommu_domain, domain);
}
static struct dma_domain *to_dma_domain(struct sunway_iommu_domain *sdomain)
{
return container_of(sdomain, struct dma_domain, sdomain);
}
static void add_domain_to_list(struct sunway_iommu_domain *sdomain)
{
unsigned long flags;
spin_lock_irqsave(&sunway_domain_lock, flags);
list_add(&sdomain->list, &sunway_domain_list);
spin_unlock_irqrestore(&sunway_domain_lock, flags);
}
static void del_domain_from_list(struct sunway_iommu_domain *sdomain)
{
unsigned long flags;
spin_lock_irqsave(&sunway_domain_lock, flags);
list_del(&sdomain->list);
spin_unlock_irqrestore(&sunway_domain_lock, flags);
}
static void free_pagetable(struct sunway_iommu_domain *sdomain)
{
unsigned long *l2_pte, *l3_pte;
unsigned long l2_pte_val, l3_pte_val;
int l2_index, l3_index, ptes_one_page;
l2_pte = sdomain->pt_root;
if (!l2_pte)
return;
ptes_one_page = PAGE_SIZE/sizeof(unsigned long);
for (l2_index = 0; l2_index < ptes_one_page; l2_index++, l2_pte++) {
l2_pte_val = *l2_pte;
if ((l2_pte_val & SW64_IOMMU_ENTRY_VALID) == 0)
continue;
l2_pte_val &= ~(SW64_IOMMU_ENTRY_VALID) & PAGE_MASK;
l2_pte_val |= PAGE_OFFSET;
l3_pte = (unsigned long *)l2_pte_val;
for (l3_index = 0; l3_index < ptes_one_page; l3_index++, l3_pte++) {
l3_pte_val = *l3_pte;
if ((l3_pte_val & SW64_IOMMU_ENTRY_VALID) == 0)
continue;
l3_pte_val &= ~(SW64_IOMMU_ENTRY_VALID) & PAGE_MASK;
l3_pte_val |= PAGE_OFFSET;
free_page(l3_pte_val);
}
free_page(l2_pte_val);
}
free_page((unsigned long)sdomain->pt_root);
}
static void domain_id_free(int id)
{
spin_lock(&domain_bitmap_lock);
if (id > 0)
__clear_bit(id, sunway_iommu_domain_bitmap);
spin_unlock(&domain_bitmap_lock);
}
static void dma_domain_free(struct dma_domain *dma_dom)
{
if (!dma_dom)
return;
del_domain_from_list(&dma_dom->sdomain);
put_iova_domain(&dma_dom->iovad);
free_pagetable(&dma_dom->sdomain);
if (dma_dom->sdomain.id)
domain_id_free(dma_dom->sdomain.id);
kfree(dma_dom);
}
static void sunway_domain_free(struct sunway_iommu_domain *sdomain)
{
if (!sdomain)
return;
del_domain_from_list(sdomain);
if (sdomain->id)
domain_id_free(sdomain->id);
kfree(sdomain);
}
static u16 sunway_domain_id_alloc(void)
{
int id;
spin_lock(&domain_bitmap_lock);
id = find_first_zero_bit(sunway_iommu_domain_bitmap, MAX_DOMAIN_NUM);
if (id > 0 && id < MAX_DOMAIN_NUM)
__set_bit(id, sunway_iommu_domain_bitmap);
else
id = 0;
spin_unlock(&domain_bitmap_lock);
return id;
}
static int sunway_domain_init(struct sunway_iommu_domain *sdomain)
{
spin_lock_init(&sdomain->lock);
mutex_init(&sdomain->api_lock);
sdomain->id = sunway_domain_id_alloc();
if (!sdomain->id)
return -ENOMEM;
INIT_LIST_HEAD(&sdomain->dev_list);
return 1;
}
static struct sunway_iommu_domain *sunway_domain_alloc(void)
{
struct sunway_iommu_domain *sdomain;
sdomain = kzalloc(sizeof(struct sunway_iommu_domain), GFP_KERNEL);
if (!sdomain)
return NULL;
if (!sunway_domain_init(sdomain)) {
kfree(sdomain);
return NULL;
}
add_domain_to_list(sdomain);
return sdomain;
}
static struct dma_domain *dma_domain_alloc(void)
{
struct dma_domain *dma_dom;
struct page;
dma_dom = kzalloc(sizeof(struct dma_domain), GFP_KERNEL);
if (!dma_dom)
return NULL;
sunway_domain_init(&dma_dom->sdomain);
dma_dom->sdomain.type = IOMMU_DOMAIN_DMA;
init_iova_domain(&dma_dom->iovad, PAGE_SIZE, IOVA_PFN(SW64_DMA_START));
reserve_iova(&dma_dom->iovad, (0xe0000000UL >> PAGE_SHIFT), (0x100000000UL >> PAGE_SHIFT));
add_domain_to_list(&dma_dom->sdomain);
return dma_dom;
}
static void device_flush_all(struct sunway_iommu_dev *sdata)
{
struct pci_controller *hose = pci_bus_to_pci_controller(sdata->pdev->bus);
if (hose == NULL)
return;
write_piu_ior0(hose->node, hose->index, DTLB_FLUSHDEV, sdata->devid);
write_piu_ior0(hose->node, hose->index, PTLB_FLUSHDEV, sdata->devid);
write_piu_ior0(hose->node, hose->index, PCACHE_FLUSHDEV, sdata->devid);
}
/* iommu_ops device attach/unattach helpers */
static void
set_dte_entry(struct sunway_iommu_dev *sdev, struct sunway_iommu_domain *sdomain)
{
struct sunway_iommu *iommu;
struct pci_dev *pdev;
struct page *dt_page, *pt_page;
unsigned long *dte_l1, *dte_l2;
unsigned long dte_l1_val, dte_l2_base, dte_l2_val;
pdev = sdev->pdev;
if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
return;
sdev->devid = PCI_DEVID(pdev->bus->number, pdev->devfn);
iommu = sdev->iommu;
dte_l1 = iommu->iommu_dtbr + (pdev->bus->number);
dte_l1_val = *dte_l1;
if (!dte_l1_val) {
/* Alloc a new level-2 device table page */
dt_page = alloc_pages_node(iommu->node, GFP_KERNEL | __GFP_ZERO,
get_order(PAGE_SIZE));
if (!dt_page) {
pr_err("Allocating a new level-2 device table page failed.\n");
return;
}
dte_l2_base = (unsigned long)page_address(dt_page);
dte_l1_val = (__pa(dte_l2_base) & PAGE_MASK) | SW64_IOMMU_ENTRY_VALID;
*dte_l1 = dte_l1_val;
}
if (!sdomain->pt_root) {
pt_page = alloc_pages_node(iommu->node, GFP_KERNEL | __GFP_ZERO, 0);
if (!pt_page) {
pr_err("Allocating pt_root failed!\n");
return;
}
sdomain->pt_root = page_address(pt_page);
}
dte_l2 = __va(dte_l1_val & ~(SW64_IOMMU_ENTRY_VALID) & PAGE_MASK) + (pdev->devfn << 3);
dte_l2_val = (__pa(sdomain->pt_root) & PAGE_MASK) | SW64_IOMMU_ENTRY_VALID;
if (sdomain->type == IOMMU_DOMAIN_IDENTITY) {
dte_l2_val |= 0x1;
sdev->passthrough = IDENTMAP_ALL;
}
*dte_l2 = dte_l2_val;
device_flush_all(sdev);
}
static void
do_attach(struct sunway_iommu_dev *sdev_data, struct sunway_iommu_domain *sdomain)
{
sdev_data->domain = sdomain;
list_add(&sdev_data->list, &sdomain->dev_list);
sdomain->dev_cnt++;
set_dte_entry(sdev_data, sdomain);
pr_debug("iommu: device %d add to domain: %d\n",
sdev_data->devid, sdomain->id);
}
static void do_detach(struct sunway_iommu_dev *sdev_data)
{
struct sunway_iommu_domain *sdomain = sdev_data->domain;
sdev_data->domain = NULL;
list_del(&sdev_data->list);
device_flush_all(sdev_data);
sdomain->dev_cnt--;
pr_debug("iommu: device %d detached from domain %d\n",
sdev_data->devid, sdomain->id);
}
static int
__attach_device(struct sunway_iommu_dev *sdev_data, struct sunway_iommu_domain *sdomain)
{
int ret;
spin_lock(&sdomain->lock);
ret = -EBUSY;
if (sdev_data->domain != NULL)
goto out_unlock;
do_attach(sdev_data, sdomain);
ret = 0;
out_unlock:
spin_unlock(&sdomain->lock);
return ret;
}
static void __detach_device(struct sunway_iommu_dev *sunway_dev_data)
{
struct sunway_iommu_domain *domain;
domain = sunway_dev_data->domain;
spin_lock(&domain->lock);
do_detach(sunway_dev_data);
spin_unlock(&domain->lock);
}
static int attach_device(struct device *dev, struct sunway_iommu_domain *sdomain)
{
struct sunway_iommu_dev *sdev;
unsigned long flags;
int ret;
sdev = dev_iommu_priv_get(dev);
spin_lock_irqsave(&sunway_iommu_device_table_lock, flags);
ret = __attach_device(sdev, sdomain);
spin_unlock_irqrestore(&sunway_iommu_device_table_lock, flags);
return ret;
}
static void detach_device(struct device *dev)
{
struct sunway_iommu_domain *sunway_domain;
struct sunway_iommu_dev *sdev;
unsigned long flags;
sdev = dev_iommu_priv_get(dev);
sunway_domain = sdev->domain;
if (WARN_ON(!sdev->domain))
return;
spin_lock_irqsave(&sunway_iommu_device_table_lock, flags);
__detach_device(sdev);
spin_unlock_irqrestore(&sunway_iommu_device_table_lock, flags);
if (!dev_is_pci(dev))
return;
}
static struct sunway_iommu_dev *search_dev_data(u16 devid)
{
struct sunway_iommu_dev *sdev_data;
struct llist_node *node;
if (llist_empty(&dev_data_list))
return NULL;
node = dev_data_list.first;
llist_for_each_entry(sdev_data, node, dev_data_list) {
if (sdev_data->devid == devid)
return sdev_data;
}
return NULL;
}
/* dma_ops helpers*/
static struct sunway_iommu_domain *get_sunway_domain(struct device *dev)
{
struct sunway_iommu_domain *sdomain;
struct iommu_domain *domain;
struct pci_dev *pdev;
struct sunway_iommu_dev *sdev;
pdev = to_pci_dev(dev);
if (!pdev)
return ERR_PTR(-ENODEV);
sdev = dev_iommu_priv_get(dev);
sdomain = sdev->domain;
if (sdomain == NULL) {
domain = iommu_get_domain_for_dev(dev);
sdomain = to_sunway_domain(domain);
attach_device(dev, sdomain);
}
if (sdomain == NULL)
return ERR_PTR(-EBUSY);
return sdomain;
}
/**********************************************************************
*
* Following functions describe IOMMU init ops
*
**********************************************************************/
static struct sunway_iommu *sunway_iommu_early_init(struct pci_controller *hose)
{
struct sunway_iommu *iommu;
struct page *page;
unsigned long base;
hose->pci_iommu = kzalloc(sizeof(struct sunway_iommu), GFP_KERNEL);
if (!hose->pci_iommu)
return 0;
iommu = hose->pci_iommu;
spin_lock_init(&iommu->dt_lock);
iommu->node = hose->node;
if (!node_online(hose->node))
iommu->node = -1;
page = alloc_pages_node(iommu->node, __GFP_ZERO, get_order(PAGE_SIZE));
if (!page) {
pr_err("Allocating a new iommu_dtbr page failed.\n");
kfree(hose->pci_iommu);
return NULL;
}
iommu->iommu_dtbr = page_address(page);
iommu->hose_pt = hose;
iommu->index = hose->index;
iommu->enabled = true;
base = __pa(iommu->iommu_dtbr) & PAGE_MASK;
write_piu_ior0(hose->node, hose->index, DTBASEADDR, base);
return iommu;
}
unsigned long fetch_dte(struct sunway_iommu *iommu, unsigned long devid,
enum exceptype type)
{
unsigned long *dte_l1, *dte_l2;
unsigned long dte_l1_val, dte_l2_val;
if (!iommu)
return 0;
dte_l1 = iommu->iommu_dtbr + (devid >> 8);
if (type == DTE_LEVEL1)
return (unsigned long)dte_l1;
dte_l1_val = *dte_l1;
if (type == DTE_LEVEL1_VAL)
return dte_l1_val;
dte_l1_val &= (~(SW64_IOMMU_ENTRY_VALID)) & (PAGE_MASK);
dte_l1_val |= PAGE_OFFSET;
dte_l2 = (unsigned long *)(dte_l1_val + ((devid & 0xff) << 3));
if (type == DTE_LEVEL2)
return (unsigned long)dte_l2;
dte_l2_val = *dte_l2;
if (type == DTE_LEVEL2_VAL)
return dte_l2_val;
return dte_l2_val;
}
unsigned long fetch_pte(struct sunway_iommu_domain *sdomain, dma_addr_t iova,
enum exceptype type)
{
unsigned long iova_pfn;
unsigned long pte_l1_val, pte_l2_val, pte_l3_val;
unsigned long *pte_l1, *pte_l2, *pte_l3;
unsigned long pte_root;
unsigned long offset;
if (!sdomain)
return -EINVAL;
pte_root = __pa(sdomain->pt_root) & PAGE_MASK;
iova_pfn = iova >> PAGE_SHIFT;
pte_root = ((pte_root) & (~(SW64_IOMMU_ENTRY_VALID)) & (PAGE_MASK));
pte_root |= PAGE_OFFSET;
offset = ((iova_pfn >> 20) & SW64_IOMMU_LEVEL1_OFFSET) << 3;
pte_l1 = (unsigned long *)(pte_root + offset);
if (type == PTE_LEVEL1)
return (unsigned long)pte_l1;
pte_l1_val = *pte_l1;
if (type == PTE_LEVEL1_VAL)
return pte_l1_val;
pte_l1_val &= (~(SW64_IOMMU_ENTRY_VALID)) & (PAGE_MASK);
pte_l1_val |= PAGE_OFFSET;
offset = ((iova_pfn >> 10) & SW64_IOMMU_LEVEL2_OFFSET) << 3;
pte_l2 = (unsigned long *)(pte_l1_val + offset);
if (type == PTE_LEVEL2)
return (unsigned long)pte_l2;
pte_l2_val = *pte_l2;
if (type == PTE_LEVEL2_VAL)
return pte_l2_val;
pte_l2_val &= (~(SW64_IOMMU_ENTRY_VALID)) & (PAGE_MASK);
pte_l2_val |= PAGE_OFFSET;
offset = (iova_pfn & SW64_IOMMU_LEVEL3_OFFSET) << 3;
pte_l3 = (unsigned long *)(pte_l2_val + offset);
if (type == PTE_LEVEL3)
return (unsigned long)pte_l3;
pte_l3_val = *pte_l3;
if (type == PTE_LEVEL3_VAL)
return pte_l3_val;
return pte_l3_val;
}
/* IOMMU Interrupt handle */
irqreturn_t iommu_interrupt(int irq, void *dev)
{
struct pci_controller *hose = (struct pci_controller *)dev;
struct sunway_iommu_domain *sdomain;
struct sunway_iommu_dev *sdev;
unsigned long iommu_status;
unsigned long type;
unsigned long devid, dva;
iommu_status = read_piu_ior0(hose->node, hose->index, IOMMUEXCPT_STATUS);
if (!(iommu_status >> 63))
return IRQ_NONE;
type = (iommu_status >> 58) & 0xf;
devid = (iommu_status >> 36) & 0xffff;
dva = ((iommu_status & 0xffffffff) >> 3) << 13;
pr_info("%s, iommu_status = %#lx, devid %#lx, dva %#lx, ",
__func__, iommu_status, devid, dva);
sdev = search_dev_data(devid);
if (sdev == NULL) {
pr_info("no such dev!!!\n");
iommu_status &= ~(1UL << 62);
write_piu_ior0(hose->node, hose->index,
IOMMUEXCPT_STATUS, iommu_status);
return IRQ_HANDLED;
}
sdomain = sdev->domain;
switch (type) {
case DTE_LEVEL1:
pr_info("invalid level1 dte, addr:%#lx, val:%#lx\n",
fetch_dte(hose->pci_iommu, devid, DTE_LEVEL1),
fetch_dte(hose->pci_iommu, devid, DTE_LEVEL1_VAL));
break;
case DTE_LEVEL2:
pr_info("invalid level2 dte, addr:%#lx, val:%#lx\n",
fetch_dte(hose->pci_iommu, devid, DTE_LEVEL2),
fetch_dte(hose->pci_iommu, devid, DTE_LEVEL2_VAL));
break;
case PTE_LEVEL1:
pr_info("invalid level1 pte, addr: %#lx, val:%#lx\n",
fetch_pte(sdomain, dva, PTE_LEVEL1),
fetch_pte(sdomain, dva, PTE_LEVEL1_VAL));
iommu_status &= ~(1UL << 62);
write_piu_ior0(hose->node, hose->index,
IOMMUEXCPT_STATUS, iommu_status);
break;
case PTE_LEVEL2:
pr_info("invalid level2 pte, addr: %#lx, val: %#lx\n",
fetch_pte(sdomain, dva, PTE_LEVEL2),
fetch_pte(sdomain, dva, PTE_LEVEL2_VAL));
iommu_status &= ~(1UL << 62);
write_piu_ior0(hose->node, hose->index,
IOMMUEXCPT_STATUS, iommu_status);
break;
case PTE_LEVEL3:
pr_info("invalid level3 pte, addr: %#lx, val: %#lx\n",
fetch_pte(sdomain, dva, PTE_LEVEL3),
fetch_pte(sdomain, dva, PTE_LEVEL3_VAL));
iommu_status &= ~(1UL << 62);
write_piu_ior0(hose->node, hose->index,
IOMMUEXCPT_STATUS, iommu_status);
break;
default:
pr_info("iommu exception type %ld\n", type);
break;
}
return IRQ_HANDLED;
}
struct irqaction iommu_irqaction = {
.handler = iommu_interrupt,
.flags = IRQF_SHARED | IRQF_NO_THREAD,
.name = "sunway_iommu",
};
void sunway_enable_iommu_func(struct pci_controller *hose)
{
unsigned int iommu_irq, err;
unsigned long iommu_conf, iommu_ctrl;
iommu_irq = hose->int_irq;
pr_debug("%s node %ld rc %ld iommu_irq %d\n",
__func__, hose->node, hose->index, iommu_irq);
err = request_irq(iommu_irq, iommu_interrupt,
IRQF_SHARED, "sunway_iommu", hose);
if (err < 0)
pr_info("sw iommu request irq failed!\n");
iommu_ctrl = (1UL << 63) | (0x100UL << 10);
write_piu_ior0(hose->node, hose->index, IOMMUEXCPT_CTRL, iommu_ctrl);
iommu_conf = read_piu_ior0(hose->node, hose->index, PIUCONFIG0);
iommu_conf = iommu_conf | (0x3 << 7);
write_piu_ior0(hose->node, hose->index, PIUCONFIG0, iommu_conf);
write_piu_ior0(hose->node, hose->index, TIMEOUT_CONFIG, 0xf);
iommu_conf = read_piu_ior0(hose->node, hose->index, PIUCONFIG0);
pr_debug("SW arch configure node %ld hose-%ld iommu_conf = %#lx\n",
hose->node, hose->index, iommu_conf);
}
static bool is_iommu_enable(struct pci_controller *hose)
{
u64 rc_mask = 0x1;
rc_mask <<= (8 * hose->node + hose->index);
if (iommu_enable_cmd & rc_mask)
return true;
return false;
}
/* iommu cpu syscore ops */
static int iommu_cpu_suspend(void)
{
return 0;
}
static void iommu_cpu_resume(void)
{
}
struct syscore_ops iommu_cpu_syscore_ops = {
.suspend = iommu_cpu_suspend,
.resume = iommu_cpu_resume,
};
static struct iommu_domain *sunway_iommu_domain_alloc(unsigned int type);
static int sunway_iommu_init(void)
{
struct pci_controller *hose;
struct sunway_iommu *iommu;
int ret;
int iommu_index = 0;
sunway_iommu_domain_bitmap =
(void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
get_order(MAX_DOMAIN_NUM / 8));
if (sunway_iommu_domain_bitmap == NULL)
return 0;
__set_bit(0, sunway_iommu_domain_bitmap);
/* Do the loop */
for (hose = hose_head; hose; hose = hose->next) {
if (!is_iommu_enable(hose)) {
hose->iommu_enable = false;
continue;
}
iommu = sunway_iommu_early_init(hose);
if (!iommu) {
pr_err("Allocating sunway_iommu failed\n");
hose->iommu_enable = false;
continue;
}
iommu_device_sysfs_add(&iommu->iommu, NULL, NULL, "%d",
iommu_index);
iommu_device_set_ops(&iommu->iommu, &sunway_iommu_ops);
iommu_device_register(&iommu->iommu);
iommu_index++;
sunway_enable_iommu_func(hose);
hose->iommu_enable = true;
}
ret = iova_cache_get();
if (ret)
return ret;
ret = bus_set_iommu(&pci_bus_type, &sunway_iommu_ops);
if (ret)
return ret;
for (hose = hose_head; hose; hose = hose->next)
if (hose->iommu_enable)
piu_flush_all(hose);
register_syscore_ops(&iommu_cpu_syscore_ops);
return 1;
}
subsys_initcall_sync(sunway_iommu_init);
/*******************************************************************************
*
* DMA OPS Functions
*
******************************************************************************/
struct sunway_iommu *get_first_iommu_from_domain(struct sunway_iommu_domain *sdomain)
{
struct sunway_iommu *iommu;
struct sunway_iommu_dev *entry;
entry = list_first_entry(&sdomain->dev_list, struct sunway_iommu_dev, list);
iommu = entry->iommu;
return iommu;
}
static unsigned long
sunway_iommu_unmap_page(struct sunway_iommu_domain *sunway_domain,
unsigned long iova, unsigned long page_size)
{
unsigned long offset, iova_pfn;
unsigned long *pte_base, *pte;
unsigned long grn;
int level, current_level;
int tmp = 1;
pr_debug("%s iova %#lx, page_size %#lx\n", __func__, iova, page_size);
BUG_ON(!is_power_of_2(page_size));
switch (page_size) {
case (1UL << 33):
level = 1;
grn = PTE_GRN_8G;
break;
case (1UL << 29):
level = 2;
grn = PTE_GRN_512M;
break;
case (1UL << 23):
level = 2;
grn = PTE_GRN_8M;
break;
default:
level = 3;
break;
}
pte_base = sunway_domain->pt_root;
iova_pfn = iova >> PAGE_SHIFT;
offset = (iova_pfn >> 20) & 0x1ff;
current_level = 1;
while (current_level <= level) {
pte = &pte_base[offset];
if (current_level == level) {
if (grn == PTE_GRN_512M) {
int i;
for (i = 0; i < 64; i++) {
*(pte + i) = 0;
flush_pcache_by_addr(sunway_domain, (unsigned long)pte);
}
} else {
*pte = 0;
flush_pcache_by_addr(sunway_domain, (unsigned long)pte);
}
flush_ptlb_by_addr(sunway_domain, (iova >> PAGE_SHIFT));
break;
}
pte_base = (unsigned long *)((*pte & (~PTE_FLAGS_MASK)) | PAGE_OFFSET);
offset = (iova_pfn >> (tmp--) * 10) & 0x3ff;
current_level++;
}
return page_size;
}
int sunway_iommu_map_page(struct sunway_iommu_domain *sunway_domain,
unsigned long bus_addr, unsigned long paddr,
size_t page_size)
{
struct page *page;
struct sunway_iommu *iommu;
unsigned long iova_pfn, pte_val;
unsigned long *pte_base, *pte;
unsigned long offset, grn = 0;
int level = 0, current_level;
int tmp = 1;
iommu = get_first_iommu_from_domain(sunway_domain);
if (!iommu)
return -1;
iova_pfn = bus_addr >> PAGE_SHIFT;
pte_base = sunway_domain->pt_root;
switch (page_size) {
case (1UL << 33):
level = 1;
grn = PTE_GRN_8G;
break;
case (1UL << 29):
level = 2;
grn = PTE_GRN_512M;
break;
case (1UL << 23):
grn = PTE_GRN_8M;
level = 2;
break;
default:
level = 3;
break;
}
offset = (iova_pfn >> 20) & 0x1ff;
current_level = 1;
while (current_level <= level) {
pte = &pte_base[offset];
if (!(*pte) || (current_level == level)) {
pte_val = PTE_VALID | PTE_RWE | grn;
if (current_level == level) {
*(volatile u64 *)(pte) = 0;
pte_val |= ((paddr & PAGE_MASK) | LAST_STAGE);
} else {
page = alloc_pages_node(iommu->node, GFP_ATOMIC | __GFP_ZERO, 0);
if (!page) {
pr_err("Allocating level%d page table pages failed.\n", (level + 1));
return -ENOMEM;
}
pte_val |= (page_to_phys(page) & PAGE_MASK);
}
if ((grn == PTE_GRN_512M) && (current_level == 2)) {
int i;
for (i = 0; i < 64; i++) {
cmpxchg64((volatile u64 *)(pte + i), 0UL, pte_val);
flush_pcache_by_addr(sunway_domain, (unsigned long)(pte + i));
}
} else {
if (cmpxchg64((volatile u64 *)pte, 0UL, pte_val))
free_page((unsigned long)page_address(page));
else
flush_pcache_by_addr(sunway_domain, (unsigned long)pte);
}
}
pte_base = (unsigned long *)__va((*pte) & (~PTE_FLAGS_MASK));
offset = (iova_pfn >> (tmp--) * 10) & 0x3ff;
current_level++;
}
return 0;
}
static unsigned long
sunway_alloc_iova(struct dma_domain *dma_dom, unsigned long pages, struct pci_dev *pdev)
{
struct device *dev;
unsigned long pfn = 0;
pages = __roundup_pow_of_two(pages);
dev = &(pdev->dev);
if (min(dev->coherent_dma_mask, *dev->dma_mask) == DMA_BIT_MASK(32)) {
pfn = alloc_iova_fast(&dma_dom->iovad, pages,
IOVA_PFN(SW64_32BIT_DMA_LIMIT), true);
} else {
/* IOVA boundary should be 16M ~ 3.5G */
pfn = alloc_iova_fast(&dma_dom->iovad, pages,
IOVA_PFN(SW64_64BIT_DMA_LIMIT), true);
}
return (pfn << PAGE_SHIFT);
}
static void sunway_free_iova(struct dma_domain *dma_dom,
unsigned long address, unsigned long pages)
{
pages = __roundup_pow_of_two(pages);
address >>= PAGE_SHIFT;
free_iova_fast(&dma_dom->iovad, address, pages);
}
static dma_addr_t
__sunway_map_single(struct dma_domain *dma_dom,
struct pci_dev *pdev, phys_addr_t paddr, size_t size)
{
dma_addr_t ret, address, start;
unsigned long npages, i;
npages = iommu_num_pages(paddr, size, PAGE_SIZE);
address = sunway_alloc_iova(dma_dom, npages, pdev);
if (!address)
return 0;
start = address;
for (i = 0; i < npages; ++i) {
ret = sunway_iommu_map_page(&dma_dom->sdomain, start,
paddr, PAGE_SIZE);
if (ret) {
pr_info("error when map page.\n");
goto out_unmap;
}
start += PAGE_SIZE;
paddr += PAGE_SIZE;
}
address += paddr & ~PAGE_MASK;
return address;
out_unmap:
for (--i; i >= 0; --i) {
start -= PAGE_SIZE;
sunway_iommu_unmap_page(&dma_dom->sdomain, start, PAGE_SIZE);
}
sunway_free_iova(dma_dom, address, npages);
return 0;
}
static dma_addr_t
pci_iommu_map_single(struct pci_dev *pdev,
struct dma_domain *dma_dom, void *cpu_addr, size_t size)
{
struct pci_controller *hose = pci_bus_to_pci_controller(pdev->bus);
unsigned long paddr;
if (hose == NULL) {
pr_err("%s: hose does not exist!\n", __func__);
return 0;
}
paddr = __sunway_map_single(dma_dom, pdev, __pa(cpu_addr), size);
pr_debug("pci_alloc_consistent: %zx -> [%px,%lx] from %ps\n",
size, cpu_addr, paddr, __builtin_return_address(0));
return paddr;
}
static void *sunway_alloc_coherent(struct device *dev,
size_t size,
dma_addr_t *dma_addr, gfp_t gfp,
unsigned long attrs)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_controller *hose;
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
struct sunway_iommu_dev *sdev;
struct page *page;
void *cpu_addr;
if (!pdev)
return NULL;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose)
return NULL;
gfp &= ~GFP_DMA;
try_again:
page = alloc_pages_node(dev_to_node(dev), gfp | __GFP_ZERO, get_order(size));
if (!page) {
pr_err("Allocating pages failed.\n");
return NULL;
}
cpu_addr = page_address(page);
if (!cpu_addr) {
pr_info
("pci_alloc_consistent: get_free_pages failed from %ps\n",
__builtin_return_address(0));
return NULL;
}
*dma_addr = __pa(cpu_addr);
if (!(hose->iommu_enable))
return cpu_addr;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough & DMA_MASK64)
return cpu_addr;
else if (sdev->passthrough) {
if (min(dev->coherent_dma_mask, *dev->dma_mask) > DMA_BIT_MASK(32)) {
sdev->passthrough |= DMA_MASK64;
return cpu_addr;
}
__free_pages(page, get_order(size));
set_dma_ops(dev, get_arch_dma_ops(dev->bus));
return dev->dma_ops->alloc(dev, size, dma_addr, gfp, attrs);
}
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
*dma_addr = pci_iommu_map_single(pdev, dma_dom, cpu_addr, size);
if (*dma_addr == 0) {
free_pages((unsigned long)cpu_addr, get_order(size));
if (gfp & GFP_DMA)
return NULL;
gfp |= GFP_DMA;
goto try_again;
}
return cpu_addr;
}
static void
__sunway_unmap_single(struct dma_domain *dma_dom, dma_addr_t dma_addr, size_t size)
{
dma_addr_t start;
unsigned long npages;
int i;
npages = iommu_num_pages(dma_addr, size, PAGE_SIZE);
dma_addr &= PAGE_MASK;
start = dma_addr;
for (i = 0; i < npages; i++) {
sunway_iommu_unmap_page(&dma_dom->sdomain, start, PAGE_SIZE);
start += PAGE_SIZE;
}
sunway_free_iova(dma_dom, dma_addr, npages);
pr_debug("pci_free_consistent: %zx -> [%llx] from %ps\n",
size, dma_addr, __builtin_return_address(0));
}
static void
sunway_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_addr, unsigned long attrs)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
if (!pdev)
goto out_unmap;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose || !(hose->iommu_enable))
goto out_unmap;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough)
goto out_unmap;
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
__sunway_unmap_single(dma_dom, dma_addr, size);
goto out_free;
out_unmap:
pci_unmap_single(pdev, dma_addr, size, PCI_DMA_BIDIRECTIONAL);
out_free:
pr_debug("sunway_free_consistent: [%llx,%zx] from %ps\n",
dma_addr, size, __builtin_return_address(0));
free_pages((unsigned long)vaddr, get_order(size));
}
static dma_addr_t
sunway_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction dir, unsigned long attrs)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
phys_addr_t paddr = page_to_phys(page) + offset;
if (!pdev)
return 0;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose || !(hose->iommu_enable))
return paddr;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough & DMA_MASK64)
return paddr;
else if (sdev->passthrough) {
if (min(dev->coherent_dma_mask, *dev->dma_mask) > DMA_BIT_MASK(32)) {
sdev->passthrough |= DMA_MASK64;
return paddr;
}
set_dma_ops(dev, get_arch_dma_ops(dev->bus));
return dev->dma_ops->map_page(dev, page, offset, size, dir, attrs);
}
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
return pci_iommu_map_single(pdev, dma_dom,
(char *)page_address(page) + offset, size);
}
static void
sunway_unmap_page(struct device *dev, dma_addr_t dma_addr,
size_t size, enum dma_data_direction dir, unsigned long attrs)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
struct pci_dev *pdev;
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
pdev = to_pci_dev(dev);
if (!pdev)
return;
hose = pci_bus_to_pci_controller(pdev->bus);
if (hose == NULL)
return;
if (!hose->iommu_enable)
return;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough)
return;
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
__sunway_unmap_single(dma_dom, dma_addr, size);
}
#define SG_ENT_VIRT_ADDRESS(SG) (sg_virt((SG)))
static int
sunway_map_sg(struct device *dev, struct scatterlist *sgl,
int nents, enum dma_data_direction dir, unsigned long attrs)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom = NULL;
struct scatterlist *sg;
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
int i, out_nents = 0;
if (dir == PCI_DMA_NONE)
BUG();
if (!pdev)
return 0;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose)
return 0;
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
for_each_sg(sgl, sg, nents, i) {
BUG_ON(!sg_page(sg));
sg_dma_address(sg) = __pa(SG_ENT_VIRT_ADDRESS(sg));
if (!(hose->iommu_enable))
goto check;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough & DMA_MASK64)
goto check;
else if (sdev->passthrough) {
if (min(dev->coherent_dma_mask, *dev->dma_mask) > DMA_BIT_MASK(32)) {
sdev->passthrough |= DMA_MASK64;
goto check;
}
set_dma_ops(dev, get_arch_dma_ops(dev->bus));
return dev->dma_ops->map_sg(dev, sgl, nents, dir, attrs);
}
sg_dma_address(sg) =
pci_iommu_map_single(pdev, dma_dom,
SG_ENT_VIRT_ADDRESS(sg), sg->length);
check:
if (sg_dma_address(sg) == 0)
goto error;
sg_dma_len(sg) = sg->length;
out_nents++;
}
return nents;
error:
pr_warn("pci_map_sg failed:");
pr_warn("could not allocate dma page tables\n");
if (out_nents)
pci_unmap_sg(pdev, sgl, out_nents, dir);
return 0;
}
static void
sunway_unmap_sg(struct device *dev, struct scatterlist *sgl,
int nents, enum dma_data_direction dir, unsigned long attrs)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
struct scatterlist *sg;
struct pci_dev *pdev;
struct pci_controller *hose;
struct sunway_iommu_dev *sdev;
dma_addr_t dma_addr;
long size;
int j;
pdev = to_pci_dev(dev);
if (!pdev)
return;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose->iommu_enable)
return;
sdev = dev_iommu_priv_get(dev);
if (sdev->passthrough)
return;
sdomain = get_sunway_domain(dev);
dma_dom = to_dma_domain(sdomain);
for_each_sg(sgl, sg, nents, j) {
dma_addr = sg->dma_address;
size = sg->dma_length;
if (!size)
break;
__sunway_unmap_single(dma_dom, dma_addr, size);
}
}
static const struct dma_map_ops sunway_dma_ops = {
.alloc = sunway_alloc_coherent,
.free = sunway_free_coherent,
.map_sg = sunway_map_sg,
.unmap_sg = sunway_unmap_sg,
.map_page = sunway_map_page,
.unmap_page = sunway_unmap_page,
.dma_supported = dma_direct_supported,
};
/**********************************************************************
*
* IOMMU OPS Functions
*
**********************************************************************/
static struct iommu_domain *sunway_iommu_domain_alloc(unsigned int type)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
switch (type) {
case IOMMU_DOMAIN_UNMANAGED:
sdomain = sunway_domain_alloc();
if (!sdomain) {
pr_err("Allocating sunway_domain failed!\n");
return NULL;
}
sdomain->domain.geometry.aperture_start = 0UL;
sdomain->domain.geometry.aperture_end = ~0ULL;
sdomain->domain.geometry.force_aperture = true;
sdomain->type = IOMMU_DOMAIN_UNMANAGED;
break;
case IOMMU_DOMAIN_DMA:
dma_dom = dma_domain_alloc();
if (!dma_dom) {
pr_err("Failed to alloc dma domain!\n");
return NULL;
}
sdomain = &dma_dom->sdomain;
break;
case IOMMU_DOMAIN_IDENTITY:
sdomain = sunway_domain_alloc();
if (!sdomain)
return NULL;
sdomain->type = IOMMU_DOMAIN_IDENTITY;
break;
default:
return NULL;
}
return &sdomain->domain;
}
static void clean_domain(struct sunway_iommu_domain *sdomain)
{
struct sunway_iommu_dev *entry;
unsigned long flags;
spin_lock_irqsave(&sunway_iommu_device_table_lock, flags);
while (!list_empty(&sdomain->dev_list)) {
entry = list_first_entry(&sdomain->dev_list,
struct sunway_iommu_dev, list);
BUG_ON(!entry->domain);
__detach_device(entry);
}
spin_unlock_irqrestore(&sunway_iommu_device_table_lock, flags);
}
static void sunway_iommu_domain_free(struct iommu_domain *dom)
{
struct sunway_iommu_domain *sdomain;
struct dma_domain *dma_dom;
sdomain = to_sunway_domain(dom);
if (sdomain->dev_cnt > 0)
clean_domain(sdomain);
BUG_ON(sdomain->dev_cnt != 0);
if (!dom)
return;
switch (dom->type) {
case IOMMU_DOMAIN_DMA:
dma_dom = to_dma_domain(sdomain);
dma_domain_free(dma_dom);
break;
default:
free_pagetable(sdomain);
sunway_domain_free(sdomain);
break;
}
}
static int sunway_iommu_attach_device(struct iommu_domain *dom, struct device *dev)
{
struct sunway_iommu_domain *sdomain = to_sunway_domain(dom);
struct sunway_iommu_dev *sdev;
struct pci_dev *pdev;
struct pci_controller *hose;
int ret;
pdev = to_pci_dev(dev);
if (!pdev)
return -EINVAL;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose)
return -EINVAL;
if (!hose->iommu_enable)
return -EINVAL;
sdev = dev_iommu_priv_get(dev);
if (!sdev)
return -EINVAL;
if (sdev->domain)
detach_device(dev);
ret = attach_device(dev, sdomain);
return ret;
}
static void sunway_iommu_detach_device(struct iommu_domain *dom, struct device *dev)
{
struct sunway_iommu_dev *sdev;
struct pci_dev *pdev = to_pci_dev(dev);
if (!pdev)
return;
sdev = dev_iommu_priv_get(dev);
if (sdev->domain != NULL)
detach_device(dev);
}
static phys_addr_t
sunway_iommu_iova_to_phys(struct iommu_domain *dom, dma_addr_t iova)
{
struct sunway_iommu_domain *sdomain = to_sunway_domain(dom);
unsigned long paddr, grn;
unsigned long is_last;
if (iova > SW64_BAR_ADDRESS)
return iova;
paddr = fetch_pte(sdomain, iova, PTE_LEVEL1_VAL);
if ((paddr & SW64_IOMMU_ENTRY_VALID) == 0)
return 0;
is_last = paddr & SW64_PTE_LAST_MASK;
grn = paddr & SW64_PTE_GRN_MASK;
if (is_last) {
if (grn == PTE_GRN_8G) {
paddr &= ~PTE_FLAGS_MASK;
paddr += iova & PAGE_8G_OFFSET_MASK;
return paddr;
}
return 0;
}
paddr = fetch_pte(sdomain, iova, PTE_LEVEL2_VAL);
if ((paddr & SW64_IOMMU_ENTRY_VALID) == 0)
return 0;
is_last = paddr & SW64_PTE_LAST_MASK;
grn = paddr & SW64_PTE_GRN_MASK;
if (is_last) {
if (grn == PTE_GRN_512M) {
paddr &= ~PTE_FLAGS_MASK;
paddr += iova & PAGE_512M_OFFSET_MASK;
return paddr;
}
if (grn == PTE_GRN_8M) {
paddr &= ~PTE_FLAGS_MASK;
paddr += iova & PAGE_8M_OFFSET_MASK;
return paddr;
}
return 0;
}
paddr = fetch_pte(sdomain, iova, PTE_LEVEL3_VAL);
if ((paddr & SW64_IOMMU_ENTRY_VALID) == 0)
return 0;
grn = paddr & SW64_PTE_GRN_MASK;
if (grn != 0)
return 0;
paddr &= ~PTE_FLAGS_MASK;
paddr += iova & PAGE_MASK;
return paddr;
}
static int
sunway_iommu_map(struct iommu_domain *dom, unsigned long iova,
phys_addr_t paddr, size_t page_size, int iommu_prot, gfp_t gfp)
{
struct sunway_iommu_domain *sdomain = to_sunway_domain(dom);
int ret;
/*
* As VFIO cannot distinguish between normal DMA request
* and pci device BAR, check should be introduced manually
* to avoid VFIO trying to map pci config space.
*/
if (iova > SW64_BAR_ADDRESS)
return 0;
mutex_lock(&sdomain->api_lock);
ret = sunway_iommu_map_page(sdomain, iova, paddr, page_size);
mutex_unlock(&sdomain->api_lock);
return ret;
}
static size_t
sunway_iommu_unmap(struct iommu_domain *dom, unsigned long iova,
size_t page_size,
struct iommu_iotlb_gather *gather)
{
struct sunway_iommu_domain *sdomain = to_sunway_domain(dom);
size_t unmap_size;
if (iova > SW64_BAR_ADDRESS)
return page_size;
mutex_lock(&sdomain->api_lock);
unmap_size = sunway_iommu_unmap_page(sdomain, iova, page_size);
mutex_unlock(&sdomain->api_lock);
return unmap_size;
}
static struct iommu_group *sunway_iommu_device_group(struct device *dev)
{
return pci_device_group(dev);
}
static void iommu_uninit_device(struct device *dev)
{
struct sunway_iommu_dev *sdev;
sdev = dev_iommu_priv_get(dev);
if (!sdev)
return;
if (sdev->domain)
detach_device(dev);
dev_iommu_priv_set(dev, NULL);
}
static void sunway_iommu_release_device(struct device *dev)
{
struct pci_dev *pdev;
struct pci_controller *hose;
pdev = to_pci_dev(dev);
if (!pdev)
return;
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose->iommu_enable)
return;
iommu_uninit_device(dev);
}
static int iommu_init_device(struct device *dev)
{
struct sunway_iommu_dev *sdev;
struct sunway_iommu *iommu;
struct pci_dev *pdev;
struct pci_controller *hose;
if (dev_iommu_priv_get(dev))
return 0;
sdev = kzalloc(sizeof(struct sunway_iommu_dev), GFP_KERNEL);
if (!sdev)
return -ENOMEM;
pdev = to_pci_dev(dev);
hose = pci_bus_to_pci_controller(pdev->bus);
iommu = hose->pci_iommu;
llist_add(&sdev->dev_data_list, &dev_data_list);
sdev->pdev = pdev;
sdev->iommu = iommu;
dev_iommu_priv_set(dev, sdev);
return 0;
}
static struct iommu_device *sunway_iommu_probe_device(struct device *dev)
{
struct pci_dev *pdev;
struct pci_controller *hose;
struct sunway_iommu *iommu;
int ret;
pdev = to_pci_dev(dev);
if (!pdev)
return ERR_PTR(-ENODEV);
if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
return ERR_PTR(-ENODEV);
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT)
return ERR_PTR(-ENODEV);
hose = pci_bus_to_pci_controller(pdev->bus);
if (!hose)
return ERR_PTR(-ENODEV);
if (!hose->iommu_enable)
return ERR_PTR(-ENODEV);
if (dev_iommu_priv_get(dev)) {
iommu = hose->pci_iommu;
return &iommu->iommu;
}
ret = iommu_init_device(dev);
if (ret)
return ERR_PTR(ret);
iommu = hose->pci_iommu;
return &iommu->iommu;
}
static int sunway_iommu_def_domain_type(struct device *dev)
{
struct sunway_iommu_dev *sdev;
sdev = dev_iommu_priv_get(dev);
if (sdev->domain)
return 0;
return sdev->domain->type;
}
static bool sunway_iommu_capable(enum iommu_cap cap)
{
switch (cap) {
case IOMMU_CAP_INTR_REMAP:
return true;
default:
return false;
}
}
static void sunway_iommu_probe_finalize(struct device *dev)
{
struct iommu_domain *domain;
domain = iommu_get_domain_for_dev(dev);
if (domain)
set_dma_ops(dev, &sunway_dma_ops);
}
const struct iommu_ops sunway_iommu_ops = {
.capable = sunway_iommu_capable,
.domain_alloc = sunway_iommu_domain_alloc,
.domain_free = sunway_iommu_domain_free,
.attach_dev = sunway_iommu_attach_device,
.detach_dev = sunway_iommu_detach_device,
.probe_device = sunway_iommu_probe_device,
.probe_finalize = sunway_iommu_probe_finalize,
.release_device = sunway_iommu_release_device,
.map = sunway_iommu_map,
.unmap = sunway_iommu_unmap,
.iova_to_phys = sunway_iommu_iova_to_phys,
.device_group = sunway_iommu_device_group,
.pgsize_bitmap = SW64_IOMMU_PGSIZES,
.def_domain_type = sunway_iommu_def_domain_type,
};
/*****************************************************************************
*
* Boot param handle
* Each bit of iommu_enable bitmap represents an rc enable, and every 8 bits
* represents one cpu node. For example, iommu_enable=0x0100 means enabling
* rc0 for cpu node 1.
*
*****************************************************************************/
static int __init iommu_enable_setup(char *str)
{
int ret;
unsigned long rc_bitmap = 0xffffffffUL;
ret = kstrtoul(str, 16, &rc_bitmap);
iommu_enable_cmd = rc_bitmap;
return ret;
}
__setup("iommu_enable=", iommu_enable_setup);