1/* $NetBSD: fdt_iommu.c,v 1.1 2021/09/04 12:34:39 jmcneill Exp $ */ 2 3/*- 4 * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__KERNEL_RCSID(0, "$NetBSD: fdt_iommu.c,v 1.1 2021/09/04 12:34:39 jmcneill Exp $"); 31 32#include <sys/param.h> 33#include <sys/bus.h> 34#include <sys/kmem.h> 35#include <sys/queue.h> 36 37#include <libfdt.h> 38#include <dev/fdt/fdtvar.h> 39 40struct fdtbus_iommu { 41 device_t iommu_dev; 42 int iommu_phandle; 43 const struct fdtbus_iommu_func *iommu_funcs; 44 u_int iommu_cells; 45 LIST_ENTRY(fdtbus_iommu) iommu_next; 46}; 47 48static LIST_HEAD(, fdtbus_iommu) fdtbus_iommus = 49 LIST_HEAD_INITIALIZER(fdtbus_iommus); 50 51/* 52 * fdtbus_get_iommu -- 53 * 54 * Return the iommu registered with the specified node, or NULL if 55 * not found. 56 */ 57static struct fdtbus_iommu * 58fdtbus_get_iommu(int phandle) 59{ 60 struct fdtbus_iommu *iommu; 61 62 LIST_FOREACH(iommu, &fdtbus_iommus, iommu_next) { 63 if (iommu->iommu_phandle == phandle) { 64 return iommu; 65 } 66 } 67 68 return NULL; 69} 70 71/* 72 * fdtbus_register_iommu -- 73 * 74 * Register an IOMMU on the specified node. 75 */ 76int 77fdtbus_register_iommu(device_t dev, int phandle, 78 const struct fdtbus_iommu_func *funcs) 79{ 80 struct fdtbus_iommu *iommu; 81 u_int cells; 82 83 if (funcs == NULL || funcs->map == NULL) { 84 return EINVAL; 85 } 86 87 if (of_getprop_uint32(phandle, "#iommu-cells", &cells) != 0) { 88 return ENXIO; 89 } 90 91 if (fdtbus_get_iommu(phandle) != NULL) { 92 return EEXIST; 93 } 94 95 iommu = kmem_alloc(sizeof(*iommu), KM_SLEEP); 96 iommu->iommu_dev = dev; 97 iommu->iommu_phandle = phandle; 98 iommu->iommu_funcs = funcs; 99 iommu->iommu_cells = cells; 100 101 LIST_INSERT_HEAD(&fdtbus_iommus, iommu, iommu_next); 102 103 return 0; 104} 105 106/* 107 * fdtbus_iommu_map -- 108 * 109 * Get a bus dma tag that is translated by any iommus specified by 110 * the device tree. The `index` property refers to the N-th item 111 * in the list of IOMMUs specified in the "iommus" property. If an 112 * IOMMU is not found, the original bus dma tag is returned. 113 */ 114bus_dma_tag_t 115fdtbus_iommu_map(int phandle, u_int index, bus_dma_tag_t dmat) 116{ 117 struct fdtbus_iommu *iommu; 118 const u_int *p; 119 u_int n, cells; 120 int len, resid; 121 122 p = fdtbus_get_prop(phandle, "iommus", &len); 123 if (p == NULL) { 124 return dmat; 125 } 126 127 for (n = 0, resid = len; resid > 0; n++) { 128 const int iommu_phandle = 129 fdtbus_get_phandle_from_native(be32toh(p[0])); 130 if (of_getprop_uint32(iommu_phandle, "#iommu-cells", &cells)) { 131 break; 132 } 133 if (n == index) { 134 iommu = fdtbus_get_iommu(iommu_phandle); 135 if (iommu == NULL) { 136 break; 137 } 138 return iommu->iommu_funcs->map(iommu->iommu_dev, 139 cells > 0 ? &p[1] : NULL, dmat); 140 } 141 resid -= (cells + 1) * 4; 142 p += cells + 1; 143 } 144 145 return dmat; 146} 147 148/* 149 * fdtbus_iommu_map_pci -- 150 * 151 * Get a bus dma tag that is translated by any iommus specified by 152 * the device tree for PCI devices. The `rid` param is the requester 153 * ID. Returns a (maybe translated) dma tag. 154 */ 155bus_dma_tag_t 156fdtbus_iommu_map_pci(int phandle, uint32_t rid, bus_dma_tag_t dmat) 157{ 158 struct fdtbus_iommu *iommu; 159 uint32_t map_mask; 160 const u_int *p; 161 u_int cells; 162 int len; 163 164 len = 0; 165 p = fdtbus_get_prop(phandle, "iommu-map", &len); 166 KASSERT(p != NULL || len == 0); 167 168 if (of_getprop_uint32(phandle, "iommu-map-mask", &map_mask) == 0) { 169 rid &= map_mask; 170 } 171 172 while (len >= 4) { 173 const u_int rid_base = be32toh(*p++); 174 const int iommu_phandle = 175 fdtbus_get_phandle_from_native(be32toh(*p++)); 176 const u_int iommu_base = be32toh(*p++); 177 const u_int length = be32toh(*p++); 178 len -= 4; 179 180 if (iommu_phandle <= 0) { 181 continue; 182 } 183 if (of_getprop_uint32(iommu_phandle, "#iommu-cells", &cells)) { 184 continue; 185 } 186 if (cells != 1) { 187 /* 188 * The pci-iommu bindings expect iommu references with 189 * exactly one specifier cell. 190 */ 191 continue; 192 } 193 iommu = fdtbus_get_iommu(iommu_phandle); 194 if (iommu == NULL) { 195 continue; 196 } 197 198 if (rid >= rid_base && rid < rid_base + length) { 199 const uint32_t sid = rid - rid_base + iommu_base; 200 return iommu->iommu_funcs->map(iommu->iommu_dev, 201 &sid, dmat); 202 } 203 } 204 205 return dmat; 206} 207