aboutsummaryrefslogtreecommitdiff
path: root/drivers/iommu/iommu-uclass.c
blob: 72f123df55a559852baba1361c34b9daf81c17a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org>
 */

#define LOG_CATEGORY UCLASS_IOMMU

#include <common.h>
#include <dm.h>
#include <iommu.h>
#include <malloc.h>
#include <phys2bus.h>
#include <asm/io.h>

#if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA))

#if CONFIG_IS_ENABLED(PCI)
static int dev_pci_iommu_enable(struct udevice *dev)
{
	struct udevice *parent = dev->parent;
	struct udevice *dev_iommu;
	u32 *iommu_map;
	u32 iommu_map_mask, length, phandle, rid, rid_base;
	int i, count, len, ret;

	while (parent) {
		len = dev_read_size(parent, "iommu-map");
		if (len > 0)
			break;
		parent = parent->parent;
	}

	if (len <= 0)
		return 0;

	iommu_map = malloc(len);
	if (!iommu_map)
		return -ENOMEM;

	count = len / sizeof(u32);
	ret = dev_read_u32_array(parent, "iommu-map", iommu_map, count);
	if (ret < 0) {
		free(iommu_map);
		return 0;
	}

	iommu_map_mask = dev_read_u32_default(parent, "iommu-map-mask", ~0);
	rid = (dm_pci_get_bdf(dev) >> 8) & iommu_map_mask;

	/* Loop over entries until mapping is found. */
	for (i = 0; i < count; i += 4) {
		rid_base = iommu_map[i];
		phandle = iommu_map[i + 1];
		length = iommu_map[i + 3];

		if (rid < rid_base || rid >= rid_base + length)
			continue;

		ret = uclass_get_device_by_phandle_id(UCLASS_IOMMU, phandle,
						      &dev_iommu);
		if (ret) {
			debug("%s: uclass_get_device_by_ofnode failed: %d\n",
			      __func__, ret);
			free(iommu_map);
			return ret;
		}
		dev->iommu = dev_iommu;
		break;
	}

	free(iommu_map);
	return 0;
}
#endif

int dev_iommu_enable(struct udevice *dev)
{
	struct ofnode_phandle_args args;
	struct udevice *dev_iommu;
	int i, count, ret = 0;

	count = dev_count_phandle_with_args(dev, "iommus",
					    "#iommu-cells", 0);
	for (i = 0; i < count; i++) {
		ret = dev_read_phandle_with_args(dev, "iommus",
						 "#iommu-cells", 0, i, &args);
		if (ret) {
			debug("%s: dev_read_phandle_with_args failed: %d\n",
			      __func__, ret);
			return ret;
		}

		ret = uclass_get_device_by_ofnode(UCLASS_IOMMU, args.node,
						  &dev_iommu);
		if (ret) {
			debug("%s: uclass_get_device_by_ofnode failed: %d\n",
			      __func__, ret);
			return ret;
		}
		dev->iommu = dev_iommu;
	}

	if (CONFIG_IS_ENABLED(PCI) && count < 0 &&
	    device_is_on_pci_bus(dev))
		return dev_pci_iommu_enable(dev);

	return 0;
}
#endif

dma_addr_t dev_iommu_dma_map(struct udevice *dev, void *addr, size_t size)
{
	const struct iommu_ops *ops;

	if (dev->iommu) {
		ops = device_get_ops(dev->iommu);
		if (ops && ops->map)
			return ops->map(dev->iommu, addr, size);
	}

	return dev_phys_to_bus(dev, virt_to_phys(addr));
}

void dev_iommu_dma_unmap(struct udevice *dev, dma_addr_t addr, size_t size)
{
	const struct iommu_ops *ops;

	if (dev->iommu) {
		ops = device_get_ops(dev->iommu);
		if (ops && ops->unmap)
			ops->unmap(dev->iommu, addr, size);
	}
}

UCLASS_DRIVER(iommu) = {
	.id		= UCLASS_IOMMU,
	.name		= "iommu",
};