tegrakernel/kernel/kernel-4.9/drivers/iommu/of_tegra-smmu.c

427 lines
10 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (c) 2014-2017, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/list.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gfp.h>
#include <linux/pci.h>
#include <asm/dma-iommu.h>
#include <dt-bindings/memory/tegra-swgroup.h>
#include "of_tegra-smmu.h"
/*
* The ARM SMMUv2 supports at most 128 SIDs.
*/
#define SMMU_MAX_SIDS 128
#define SMMU_AFI_ASID 0x238 /* PCIE */
#define SMMU_SWGRP_ASID_BASE SMMU_AFI_ASID
/* Anonymous address space(AS) attribute */
#define ANON_IOVA_START SZ_2G
#define ANON_IOVA_SIZE SZ_1G
#define ANON_IOVA_ALIGN 0
#define ANON_IOVA_PF 0
#define ANON_IOVA_GAP 1
static LIST_HEAD(smmu_addr_spaces);
size_t tegra_smmu_of_offset(int id)
{
switch (id) {
case TEGRA_SWGROUP_DC14:
return 0x490;
case TEGRA_SWGROUP_DC12:
return 0xa88;
case TEGRA_SWGROUP_AFI...TEGRA_SWGROUP_ISP:
case TEGRA_SWGROUP_MPE...TEGRA_SWGROUP_PPCS1:
return (id - TEGRA_SWGROUP_AFI) * sizeof(u32) + SMMU_AFI_ASID;
case TEGRA_SWGROUP_SDMMC1A...63:
return (id - TEGRA_SWGROUP_SDMMC1A) * sizeof(u32) + 0xa94;
};
BUG();
}
static struct dma_iommu_mapping *tegra_smmu_of_populate_mapping(
struct device *dev,
struct smmu_map_prop *prop)
{
struct dma_iommu_mapping *map;
map = arm_iommu_create_mapping(&platform_bus_type,
(dma_addr_t)prop->iova_start,
(size_t)prop->iova_size);
if (IS_ERR(map)) {
dev_err(dev, "fail to create iommu map prop=%p\n", prop);
return NULL;
}
/* FIXME: residual data */
map->alignment = prop->alignment;
map->gap_page = !!prop->gap_page;
map->num_pf_page = prop->num_pf_page;
prop->map = map;
return map;
}
struct dma_iommu_mapping *tegra_smmu_of_get_mapping(struct device *dev,
u64 swgids,
struct list_head *asprops)
{
struct smmu_map_prop *tmp;
struct dma_iommu_mapping *map;
list_for_each_entry(tmp, asprops, list) {
if (!(swgids & tmp->swgid_mask))
continue;
if ((swgids & tmp->swgid_mask) != swgids)
dev_info(dev, "mask=%llx doesn't include swgids=%llx\n",
tmp->swgid_mask, swgids);
if (tmp->map) {
kref_get(&tmp->map->kref);
return tmp->map;
}
return tegra_smmu_of_populate_mapping(dev, tmp);
}
WARN(1, "Empty mapping for %s! Making an anonymous one.\n",
dev_name(dev));
tmp = devm_kzalloc(dev, sizeof(*tmp), GFP_KERNEL);
if (!tmp)
return NULL;
tmp->swgid_mask = swgids;
tmp->iova_start = ANON_IOVA_START;
tmp->iova_size = ANON_IOVA_SIZE;
tmp->alignment = ANON_IOVA_ALIGN;
tmp->num_pf_page = ANON_IOVA_PF;
tmp->gap_page = ANON_IOVA_GAP;
map = tegra_smmu_of_populate_mapping(dev, tmp);
if (map) {
list_add_tail(&tmp->list, asprops);
dev_info(dev, "populated map=%p for swgids=%llx\n",
map, swgids);
}
return map;
}
/*
* Parse the passed address space prop (referenced by np) into prop.
*/
static struct smmu_map_prop *
__tegra_smmu_parse_as_prop(struct device *dev, struct device_node *np,
struct list_head *asprops)
{
struct smmu_map_prop *prop;
int err;
prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL);
if (!prop)
return ERR_PTR(-ENOMEM);
err = of_property_read_u64(np, "iova-start", &prop->iova_start);
err |= of_property_read_u64(np, "iova-size", &prop->iova_size);
err |= of_property_read_u32(np, "num-pf-page", &prop->num_pf_page);
err |= of_property_read_u32(np, "gap-page", &prop->gap_page);
if (err) {
dev_err(dev, "invalid address-space-prop %s\n", np->name);
return ERR_PTR(-EINVAL);
}
err = of_property_read_u32(np, "alignment", &prop->alignment);
if (err)
prop->alignment = 0;
list_add_tail(&prop->list, asprops);
return prop;
}
/* FIXME: Add linear map logic as well */
int tegra_smmu_of_register_asprops(struct device *dev,
struct list_head *asprops)
{
int count = 0, sum_hweight = 0;
struct of_phandle_iter iter;
u64 swgid_mask = 0;
struct smmu_map_prop *prop, *temp;
int err;
of_for_each_phandle(&iter, err, dev->of_node, "domains", NULL, 2) {
struct of_phandle_args iommu_args;
iommu_args.args_count = of_phandle_iterator_args(&iter,
iommu_args.args, MAX_PHANDLE_ARGS);
if (iommu_args.args_count < 2) {
dev_err(dev,
"domains expects 2 params but %d\n",
iommu_args.args_count);
goto free_mem;
}
iommu_args.np = of_node_get(iter.node);
prop = __tegra_smmu_parse_as_prop(dev, iommu_args.np, asprops);
of_node_put(iommu_args.np);
if (IS_ERR_OR_NULL(prop))
goto free_mem;
memcpy(&prop->swgid_mask, iommu_args.args, sizeof(u64));
count += 1;
/*
* The final entry in domains property is
* domains = <... &as_prop 0xFFFFFFFF 0xFFFFFFFF>;
* This entry is similar to SYSTEM_DEFAULT
* Skip the bit overlap check for this final entry
*/
if (prop->swgid_mask != ~0ULL) {
swgid_mask |= prop->swgid_mask;
sum_hweight += hweight64(prop->swgid_mask);
}
}
if (sum_hweight == hweight64(swgid_mask))
return count;
/* check bit mask overlap in domains= property */
dev_warn(dev, "overlapping bitmaps in domains!!!");
free_mem:
list_for_each_entry_safe(prop, temp, asprops, list)
devm_kfree(dev, prop);
return 0;
}
extern u64 tegra_smmu_fixup_swgids(struct device *dev,
struct iommu_linear_map **map);
u64 tegra_smmu_of_get_swgids(struct device *dev,
const struct of_device_id *matches,
struct iommu_linear_map **area)
{
struct of_phandle_iter iter;
u64 fixup, swgids = 0;
struct device_node *np = dev->of_node;
int err;
if (dev_is_pci(dev)) {
for (;;) {
struct pci_bus *bus = to_pci_dev(dev)->bus;
if (pci_is_root_bus(bus))
break;
dev = bus->bridge;
}
np = of_get_parent(dev->of_node);
}
of_for_each_phandle(&iter, err, np, "iommus",
"#iommu-cells", 0) {
struct of_phandle_args iommu_args;
iommu_args.args_count = of_phandle_iterator_args(&iter,
iommu_args.args, MAX_PHANDLE_ARGS);
iommu_args.np = of_node_get(iter.node);
if (!of_match_node(matches, iommu_args.np)) {
of_node_put(iommu_args.np);
continue;
}
of_node_put(iommu_args.np);
if (iommu_args.args_count != 1) {
dev_err(dev, "iommus contains %d cells, expected 1\n",
iommu_args.args_count);
break;
}
swgids |= (1ULL << iommu_args.args[0]);
}
if (dev_is_pci(dev))
of_node_put(np);
swgids = swgids ? swgids : SWGIDS_ERROR_CODE;
fixup = tegra_smmu_fixup_swgids(dev, area);
if (swgids_is_error(fixup))
return swgids;
if (swgids_is_error(swgids)) {
dev_notice(dev,
"No iommus property found in DT node, got swgids from fixup(%llx)\n",
fixup);
return fixup;
}
if (swgids != fixup) {
dev_notice(dev, "fixup(%llx) is different from DT(%llx)\n",
fixup, swgids);
return fixup;
}
return swgids;
}
/*
* T186 domains parsing. Instead of parsing a bitmap parse a list of SIDs. Also,
* instead of using an external list to keep the address space props, use an
* internal list. It makes no sense to keep the tegra specific address space
* data structures in the SMMU device struct.
*/
int tegra_smmu_of_parse_sids(struct device *dev)
{
struct device_node *domain_node, *child;
struct smmu_map_prop *prop, *temp;
u16 *sid_list;
int err, i;
sid_list = kzalloc(sizeof(u16) * SMMU_MAX_SIDS, GFP_KERNEL);
if (!sid_list)
return -ENOMEM;
domain_node = of_get_child_by_name(dev->of_node, "domains");
if (!domain_node) {
err = -EINVAL;
goto free_mem;
}
for_each_child_of_node(domain_node, child) {
int ret;
phandle as;
u32 sid;
struct device_node *as_node;
struct property *property;
const __be32 *cur;
ret = of_property_read_u32(child, "address-space", &as);
if (ret) {
err = -EINVAL;
of_node_put(child);
goto free_mem;
}
as_node = of_find_node_by_phandle(as);
if (!as_node) {
err = -EINVAL;
of_node_put(child);
goto free_mem;
}
prop = __tegra_smmu_parse_as_prop(dev, as_node,
&smmu_addr_spaces);
if (IS_ERR_OR_NULL(prop)) {
err = PTR_ERR(prop);
goto free_mem;
}
prop->nr_sids = of_property_count_u32_elems(child, "sid-list");
if (prop->nr_sids < 0) {
err = -EINVAL;
goto free_mem;
}
prop->sid_list = devm_kcalloc(dev, prop->nr_sids,
sizeof(*prop->sid_list),
GFP_KERNEL);
if (!prop->sid_list) {
err = -ENOMEM;
goto free_mem;
}
/* Read the SIDs. */
i = 0;
of_property_for_each_u32(child, "sid-list",
property, cur, sid) {
sid_list[sid]++;
prop->sid_list[i++] = sid;
}
}
for (i = 0; i < SMMU_MAX_SIDS; i++) {
if (sid_list[i] > 1) {
pr_err("Duplicate SID in domains property (%d)!\n", i);
err = -EINVAL;
goto free_mem;
}
}
kfree(sid_list);
return 0;
free_mem:
kfree(sid_list);
list_for_each_entry_safe(prop, temp, &smmu_addr_spaces, list) {
if (prop->sid_list)
devm_kfree(dev, prop->sid_list);
devm_kfree(dev, prop);
}
return err;
}
/*
* A master could have multiple SIDs associated with it; however, on tegra we do
* not do that. Instead we just treat the first SID as the SID which picks the
* domain.
*/
struct dma_iommu_mapping *tegra_smmu_of_get_master_map(struct device *dev,
u16 *sids, int nr_sids)
{
struct smmu_map_prop *prop;
list_for_each_entry(prop, &smmu_addr_spaces, list) {
int i;
int found = 0;
for (i = 0; i < prop->nr_sids; i++) {
if (sids[0] == prop->sid_list[i]) {
found = 1;
break;
}
}
if (!found)
continue;
if (prop->map)
return prop->map;
/* Oh well, no pre-existing map. Populate a new one. */
return tegra_smmu_of_populate_mapping(dev, prop);
}
/*
* No mapping was found! Warn and return an error. This will prevent
* the passed device from using the SMMU unlike in previous tegra chips
* where an anonymous mapping was generated.
*/
WARN(1, "No SMMU mapping found for %s!\n", dev_name(dev));
return NULL;
}