427 lines
10 KiB
C
427 lines
10 KiB
C
/*
|
|
* 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;
|
|
}
|