aboutsummaryrefslogtreecommitdiff
path: root/drivers/iommu/iommu-uclass.c
blob: dff3239cccb151c59041e14dcc1cc0b9b52d2f9e (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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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;
	const struct iommu_ops *ops;
	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) {
			log_err("%s: Failed to parse 'iommus' property for '%s': %d\n",
				__func__, dev->name, ret);
			return ret;
		}

		ret = uclass_get_device_by_ofnode(UCLASS_IOMMU, args.node,
						  &dev_iommu);
		if (ret) {
			log_err("%s: Failed to find IOMMU device for '%s': %d\n",
				__func__, dev->name, ret);
			return ret;
		}
		dev->iommu = dev_iommu;

		if (dev->parent && dev->parent->iommu == dev_iommu)
			continue;

		ops = device_get_ops(dev->iommu);
		if (ops && ops->connect) {
			ret = ops->connect(dev);
			if (ret) {
				log_err("%s: Failed to connect '%s' to IOMMU '%s': %d\n",
					__func__, dev->name, dev->iommu->name, ret);
				return ret;
			}
		}
	}

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

	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",
};