Skip to content
Snippets Groups Projects
Commit 5162ebea authored by Susheel Khiani's avatar Susheel Khiani
Browse files

lrdp-v2: Add support for AARCH64 page table format

Some clients can generate 48/49 bit virtual address.
Enhance ramdump parser to add support for this page
table format.

Change-Id: I46d803e7fcecadc73e0ccca3422d586c00ecc610
parent 017d54fd
No related branches found
No related tags found
No related merge requests found
# Copyright (c) 2016, The Linux Foundation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
# only version 2 as published by the Free Software Foundation.
#
# This program is distributed in the hope that 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.
from sizes import SZ_4K, SZ_64K, SZ_2M, SZ_32M, SZ_1G, SZ_256G
from sizes import get_order, order_size_strings
NUM_PT_LEVEL = 4
NUM_FL_PTE = 512
NUM_SL_PTE = 512
NUM_TL_PTE = 512
NUM_LL_PTE = 512
FLSL_BASE_MASK = 0xFFFFFFFFF000
FLSL_TYPE_BLOCK = (1 << 0)
FLSL_TYPE_TABLE = (3 << 0)
FLSL_PTE_TYPE_MASK = (3 << 0)
LL_TYPE_PAGE = (3 << 0)
LL_PAGE_MASK = 0xFFFFFFFFF000
LL_AP_BITS = (0x3 << 6)
LL_CH = (0x1 << 52)
LL_AP_RO = (0x3 << 6)
LL_AP_RW = (0x1 << 6)
LL_AP_PR_RW = (0x0 << 6)
LL_AP_PR_RO = (0x2 << 6)
class FlatMapping(object):
def __init__(self, virt, phys=-1, type='[]', size=SZ_4K, mapped=False):
self.virt = virt
self.phys = phys
self.type = type
self.map_size = size
self.mapped = mapped
class CollapsedMapping(object):
def __init__(self, virt_start, virt_end, phys_start=-1, phys_end=-1,
map_type='[]', map_size=SZ_4K, mapped=False):
self.virt_start = virt_start
self.virt_end = virt_end - 1
self.phys_start = phys_start
self.phys_end = phys_end - 1
self.map_type = map_type
self.map_size = map_size
self.mapped = mapped
def add_collapsed_mapping(mappings, virt_start, virt_end, phys_start, phys_end,
map_type, map_size, mapped):
map = CollapsedMapping(virt_start, virt_end, phys_start, phys_end,
map_type, map_size, mapped)
if virt_start not in mappings:
mappings[virt_start] = map
else:
map.type = 'Duplicate'
mappings[virt_start] = map
return mappings
def create_collapsed_mapping(flat_mapping):
collapsed_mapping = {}
if len(flat_mapping.keys()) > 0:
virt_addrs = sorted(flat_mapping.keys())
start_map = prev_map = flat_mapping[virt_addrs[0]]
last_mapping = False
new_mapping = False
for virt in virt_addrs[1:]:
map = flat_mapping[virt]
if map.map_size == prev_map.map_size \
and map.type == prev_map.type \
and map.mapped == prev_map.mapped \
and not map.mapped:
new_mapping = False
if virt == virt_addrs[-1]:
last_mapping = True
else:
new_mapping = True
if new_mapping:
collapsed_mapping = add_collapsed_mapping(
collapsed_mapping, start_map.virt,
map.virt, start_map.phys,
start_map.phys + start_map.map_size,
start_map.type, start_map.map_size, start_map.mapped)
start_map = map
elif last_mapping:
collapsed_mapping = add_collapsed_mapping(
collapsed_mapping, start_map.virt,
0xFFFFFFFFFFFF + 1, start_map.phys,
start_map.phys + start_map.map_size,
start_map.type, start_map.map_size, start_map.mapped)
prev_map = map
return collapsed_mapping
def add_flat_mapping(mappings, fl_idx, sl_idx, tl_idx, ll_idx,
phy_addr, map_type, page_size, mapped):
virt = (fl_idx << 39) | (sl_idx << 30) | (tl_idx << 21) | (ll_idx << 12)
map_type_str = '[R/W]'
if map_type == LL_AP_RO:
map_type_str = '[RO]'
elif map_type == LL_AP_PR_RW:
map_type_str = '[P R/W]'
elif map_type == LL_AP_PR_RO:
map_type_str = '[P RO]'
map = FlatMapping(virt, phy_addr, map_type_str, page_size, mapped)
if virt not in mappings:
mappings[virt] = map
else:
map.type = 'Duplicate'
mappings[virt] = map
return mappings
def get_super_section_mapping_info(ramdump, pg_table, index):
phy_addr = ramdump.read_u64(pg_table, False)
current_phy_addr = -1
current_page_size = SZ_1G
current_map_type = 0
status = True
if phy_addr is not None:
current_map_type = phy_addr & LL_AP_BITS
current_phy_addr = phy_addr & 0xFFFFC0000000
else:
status = False
return (current_phy_addr, current_page_size, current_map_type, status)
def get_section_mapping_info(ramdump, pg_table, index):
phy_addr = ramdump.read_u64(pg_table, False)
current_phy_addr = -1
current_page_size = SZ_2M
current_map_type = 0
status = True
section_skip_count = 0
if phy_addr is not None:
current_map_type = phy_addr & LL_AP_BITS
if phy_addr & LL_CH:
current_phy_addr = phy_addr & 0xFFFFFE000000
current_page_size = SZ_32M
section_skip_count = 15
# Current + next 15 entries are contiguous
else:
current_phy_addr = phy_addr & 0xFFFFFFE00000
current_page_size = SZ_2M
return (current_phy_addr, current_page_size, current_map_type,
status, section_skip_count)
def get_mapping_info(ramdump, pg_table, index):
ll_pte = pg_table + (index * 8)
phy_addr = ramdump.read_u64(ll_pte, False)
current_phy_addr = -1
current_page_size = SZ_4K
current_map_type = 0
status = True
skip_count = 0
if phy_addr is not None:
current_map_type = phy_addr & LL_AP_BITS
if phy_addr & LL_TYPE_PAGE:
current_phy_addr = phy_addr & 0xFFFFFFFFF000
if phy_addr & LL_CH:
current_phy_addr = phy_addr & 0xFFFFFFFF0000
current_page_size = SZ_64K
skip_count = 15
# Current + next 15 entries are contiguous
elif phy_addr != 0:
# Error condition if at last level it is not LL_TYPE_PAGE
current_phy_addr = phy_addr
status = False
return (current_phy_addr, current_page_size, current_map_type,
status, skip_count)
def fl_entry(ramdump, fl_pte, skip_fl):
fl_pg_table_entry = ramdump.read_u64(fl_pte)
sl_pte = fl_pg_table_entry & FLSL_BASE_MASK
if skip_fl == 1:
fl_pg_table_entry = FLSL_TYPE_TABLE
sl_pte = fl_pte
# Make 1st level entry look like dummy entry of type table in
# case of only 3 level page tables and make sl_pte = fl_pte
# as we start parsing from second level.
return (fl_pg_table_entry, sl_pte)
def parse_2nd_level_table(ramdump, sl_pg_table_entry, fl_index,
sl_index, tmp_mapping):
tl_pte = sl_pg_table_entry & FLSL_BASE_MASK
section_skip_count = 0
for tl_index in range(0, NUM_TL_PTE):
tl_pg_table_entry = ramdump.read_u64(tl_pte, False)
if tl_pg_table_entry == 0 or tl_pg_table_entry is None:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, sl_index,
tl_index, 0, -1,
-1, SZ_2M, False)
tl_pte += 8
continue
tl_entry_type = tl_pg_table_entry & FLSL_PTE_TYPE_MASK
if tl_entry_type == FLSL_TYPE_TABLE:
ll_pte = tl_pg_table_entry & FLSL_BASE_MASK
skip_count = 0
for ll_index in range(0, NUM_LL_PTE):
if skip_count:
skip_count -= 1
continue
(phy_addr, page_size, map_type, status,
skip_count) = get_mapping_info(
ramdump, ll_pte,
ll_index)
if status and phy_addr != -1:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, sl_index,
tl_index, ll_index, phy_addr,
map_type, page_size, True)
else:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, sl_index,
tl_index, ll_index, -1,
-1, page_size, False)
elif tl_entry_type == FLSL_TYPE_BLOCK:
if section_skip_count:
section_skip_count -= 1
continue
(phy_addr, page_size,
map_type, status,
section_skip_count) = get_section_mapping_info(
ramdump, tl_pte, tl_index)
if status and phy_addr != -1:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, sl_index,
tl_index, 0, phy_addr,
map_type, page_size, True)
tl_pte += 8
return tmp_mapping
def create_flat_mappings(ramdump, pg_table, level):
tmp_mapping = {}
fl_pte = pg_table
skip_fl = 0
fl_range = NUM_FL_PTE
read_virtual = False
if level == 3:
skip_fl = 1
fl_range = 1
read_virtual = True
# In case we have only 3 level page table we want to skip first level
# and just parse second, third and last level. To keep unify code for 3
# level and 4 level parsing just run first level loop once and directly
# jump to start parsing from second level
for fl_index in range(0, fl_range):
(fl_pg_table_entry, sl_pte) = fl_entry(ramdump, fl_pte, skip_fl)
if fl_pg_table_entry == 0:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, 0, 0, 0,
-1, -1, SZ_256G, False)
fl_pte += 8
continue
for sl_index in range(0, NUM_SL_PTE):
sl_pg_table_entry = ramdump.read_u64(sl_pte, read_virtual)
if sl_pg_table_entry == 0 or sl_pg_table_entry is None:
tmp_mapping = add_flat_mapping(tmp_mapping,
fl_index, sl_index, 0, 0,
-1, -1, SZ_1G, False)
sl_pte += 8
continue
sl_entry_type = sl_pg_table_entry & FLSL_PTE_TYPE_MASK
if sl_entry_type == FLSL_TYPE_TABLE:
tmp_mapping = parse_2nd_level_table(ramdump, sl_pg_table_entry,
fl_index, sl_index,
tmp_mapping)
elif sl_entry_type == FLSL_TYPE_BLOCK:
(phy_addr, page_size, map_type, status) \
= get_super_section_mapping_info(ramdump, sl_pte, sl_index)
if status and phy_addr != -1:
tmp_mapping = add_flat_mapping(
tmp_mapping, fl_index, sl_index, 0, 0,
phy_addr, map_type, page_size, True)
sl_pte += 8
fl_pte += 8
return tmp_mapping
def parse_aarch64_tables(ramdump, d, domain_num):
fname = 'arm_iommu_domain_%02d.txt' % (domain_num)
with ramdump.open_file(fname) as outfile:
redirect = 'OFF'
if d.redirect is None:
redirect = 'UNKNOWN'
elif d.redirect > 0:
redirect = 'ON'
iommu_context = ' '.join('%s (%s)' % (name, num)
for (name, num) in d.ctx_list)
iommu_context = iommu_context or 'None attached'
outfile.write(
'IOMMU Context: %s. Domain: %s'
'[L2 cache redirect for page tables is %s]\n' % (
iommu_context, d.client_name, redirect))
outfile.write(
'[VA Start -- VA End ] [Size ] [PA Start -- PA End ] '
'[Attributes][Page Table Entry Size]\n')
if d.pg_table == 0:
outfile.write(
'No Page Table Found. (Probably a secure domain)\n')
else:
flat_mapping = create_flat_mappings(ramdump, d.pg_table, d.level)
collapsed_mapping = create_collapsed_mapping(flat_mapping)
for virt in sorted(collapsed_mapping.keys()):
mapping = collapsed_mapping[virt]
if mapping.mapped:
outfile.write(
'0x%x--0x%x [0x%x] A:0x%x--0x%x [0x%x] %s[%s] \n' %
(mapping.virt_start, mapping.virt_end,
mapping.map_size, mapping.phys_start,
mapping.phys_end, mapping.map_size, mapping.map_type,
order_size_strings[get_order(mapping.map_size)]))
else:
outfile.write(
'0x%x--0x%x [0x%x] [UNMAPPED]\n' %
(mapping.virt_start, mapping.virt_end,
mapping.virt_end - mapping.virt_start))
# Copyright (c) 2014, The Linux Foundation. All rights reserved.
# Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
......@@ -12,31 +12,115 @@
import rb_tree
import linux_list as llist
ARM_SMMU_DOMAIN = 0
MSM_SMMU_DOMAIN = 1
class Domain(object):
def __init__(self, domain_num, pg_table, redirect, ctx_list, client_name):
def __init__(self, pg_table, redirect, ctx_list, client_name,
domain_type=MSM_SMMU_DOMAIN, level=4, domain_num=-1):
self.domain_num = domain_num
self.pg_table = pg_table
self.redirect = redirect
self.ctx_list = ctx_list
self.client_name = client_name
self.level = level
self.domain_type = domain_type
def __repr__(self):
return "#%d: %s" % (self.domain_num, self.client_name)
class IommuLib(object):
def __init__(self, ramdump):
self.ramdump = ramdump
self.domain_list = []
root = self.ramdump.read_word('domain_root')
if root is None:
list_head_attachments = self.ramdump.read_pointer(
'iommu_debug_attachments')
if list_head_attachments is not None:
list_head_arm_addr = self.ramdump.read_structure_field(
list_head_attachments, 'struct list_head', 'prev')
list_walker = llist.ListWalker(
self.ramdump, list_head_arm_addr,
self.ramdump.field_offset('struct iommu_debug_attachment',
'list'))
list_walker.walk(list_head_attachments,
self._iommu_domain_find_default,
self.domain_list)
if root is not None:
rb_walker = rb_tree.RbTreeWalker(self.ramdump)
rb_walker.walk(root, self._iommu_domain_func, self.domain_list)
def _iommu_domain_find_default(self, node, domain_list):
domain_ptr = self.ramdump.read_structure_field(
node, 'struct iommu_debug_attachment', 'domain')
priv_ptr = self.ramdump.read_structure_field(
domain_ptr, 'struct iommu_domain', 'priv')
if not (domain_ptr and priv_ptr):
return
rb_walker = rb_tree.RbTreeWalker(self.ramdump)
rb_walker.walk(root, self._iommu_domain_func, self.domain_list)
arm_smmu_ops = self.ramdump.address_of('arm_smmu_ops')
dev_ptr = self.ramdump.read_structure_field(
node, 'struct iommu_debug_attachment', 'dev')
kobj_ptr = dev_ptr + self.ramdump.field_offset('struct device', 'kobj')
client_name = self.ramdump.read_structure_cstring(
kobj_ptr, 'struct kobject', 'name')
iommu_domain_ops = self.ramdump.read_structure_field(
domain_ptr, 'struct iommu_domain', 'ops')
if iommu_domain_ops == arm_smmu_ops:
pgtbl_ops_ptr = self.ramdump.read_structure_field(
priv_ptr, 'struct arm_smmu_domain', 'pgtbl_ops')
io_pgtable_ptr = self.ramdump.container_of(
pgtbl_ops_ptr, 'struct io_pgtable', 'ops')
arm_lpae_io_pgtable_ptr = self.ramdump.container_of(
io_pgtable_ptr, 'struct arm_lpae_io_pgtable', 'iop')
pg_table = self.ramdump.read_structure_field(
arm_lpae_io_pgtable_ptr, 'struct arm_lpae_io_pgtable', 'pgd')
level = self.ramdump.read_structure_field(
arm_lpae_io_pgtable_ptr, 'struct arm_lpae_io_pgtable',
'levels')
domain_create = Domain(pg_table, 0, [], client_name,
ARM_SMMU_DOMAIN, level)
domain_list.append(domain_create)
else:
priv_pt_offset = self.ramdump.field_offset('struct msm_iommu_priv',
'pt')
pgtable_offset = self.ramdump.field_offset('struct msm_iommu_pt',
'fl_table')
redirect_offset = self.ramdump.field_offset('struct msm_iommu_pt',
'redirect')
if priv_pt_offset is not None:
pg_table = self.ramdump.read_u64(
priv_ptr + priv_pt_offset + pgtable_offset)
redirect = self.ramdump.read_u64(
priv_ptr + priv_pt_offset + redirect_offset)
domain_create = Domain(pg_table, redirect, [],
client_name)
domain_list.append(domain_create)
def _iommu_list_func(self, node, ctx_list):
ctx_drvdata_name_ptr = self.ramdump.read_word(
node + self.ramdump.field_offset('struct msm_iommu_ctx_drvdata', 'name'))
node + self.ramdump.field_offset('struct msm_iommu_ctx_drvdata',
'name'))
ctxdrvdata_num_offset = self.ramdump.field_offset(
'struct msm_iommu_ctx_drvdata', 'num')
num = self.ramdump.read_u32(node + ctxdrvdata_num_offset)
......@@ -70,13 +154,17 @@ class IommuLib(object):
'struct msm_iommu_priv', 'list_attached')
if list_attached_offset is not None:
list_attached = self.ramdump.read_word(priv_ptr + list_attached_offset)
list_attached = self.ramdump.read_word(priv_ptr +
list_attached_offset)
else:
list_attached = None
priv_pt_offset = self.ramdump.field_offset('struct msm_iommu_priv', 'pt')
pgtable_offset = self.ramdump.field_offset('struct msm_iommu_pt', 'fl_table')
redirect_offset = self.ramdump.field_offset('struct msm_iommu_pt', 'redirect')
priv_pt_offset = self.ramdump.field_offset('struct msm_iommu_priv',
'pt')
pgtable_offset = self.ramdump.field_offset('struct msm_iommu_pt',
'fl_table')
redirect_offset = self.ramdump.field_offset('struct msm_iommu_pt',
'redirect')
if priv_pt_offset is not None:
pg_table = self.ramdump.read_word(
......@@ -87,12 +175,14 @@ class IommuLib(object):
# On some builds we are unable to look up the offsets so hardcode
# the offsets.
pg_table = self.ramdump.read_word(priv_ptr + 0)
redirect = self.ramdump.read_u32(priv_ptr + self.ramdump.sizeof('void *'))
# Note: On some code bases we don't have this pg_table and redirect in the priv structure (see msm_iommu_sec.c). It only
# contains list_attached. If this is the case we can detect that by checking whether
# pg_table == redirect (prev == next pointers of the attached
# list).
redirect = self.ramdump.read_u32(priv_ptr +
self.ramdump.sizeof('void *'))
# Note: On some code bases we don't have this pg_table and redirect
# in the priv structure (see msm_iommu_sec.c). It only contains
# list_attached. If this is the case we can detect that by checking
# whether pg_table == redirect (prev == next pointers of the
# attached list).
if pg_table == redirect:
# This is a secure domain. We don't have access to the page
# tables.
......@@ -103,8 +193,10 @@ class IommuLib(object):
if list_attached is not None and list_attached != 0:
list_walker = llist.ListWalker(
self.ramdump, list_attached,
self.ramdump.field_offset('struct msm_iommu_ctx_drvdata', 'attached_elm'))
self.ramdump.field_offset('struct msm_iommu_ctx_drvdata',
'attached_elm'))
list_walker.walk(list_attached, self._iommu_list_func, ctx_list)
domain_list.append(
Domain(domain_num, pg_table, redirect, ctx_list, client_name))
Domain(pg_table, redirect, ctx_list, client_name,
domain_num=domain_num))
......@@ -157,8 +157,9 @@ def get_coalesced_mappings(flat_mappings):
cc = OrderedDict(sorted(cc.items()))
return cc
def parse_long_form_tables(dump, d):
fname = 'msm_iommu_domain_%02d.txt' % (d.domain_num)
def parse_long_form_tables(dump, d, domain_num):
fname = 'msm_iommu_domain_%02d.txt' % (domain_num)
with dump.open_file(fname) as outfile:
print_out_str('LPAE Iommu page tables: ' + fname)
......
# Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
# Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
......@@ -14,8 +14,10 @@ import math
from print_out import print_out_str
from parser_util import register_parser, RamParser
from sizes import SZ_4K, SZ_64K, SZ_1M, SZ_16M, get_order, order_size_strings
from iommulib import IommuLib
from iommulib import IommuLib, MSM_SMMU_DOMAIN
from lpaeiommulib import parse_long_form_tables
from aarch64iommulib import parse_aarch64_tables
@register_parser('--print-iommu-pg-tables', 'Print IOMMU page tables')
class IOMMU(RamParser):
......@@ -303,9 +305,9 @@ class IOMMU(RamParser):
self.out_file.write('0x%08x--0x%08x [0x%08x] [UNMAPPED]\n' %
(mapping.virt_start, mapping.virt_end, mapping.virt_size()))
def parse_short_form_tables(self, d):
def parse_short_form_tables(self, d, domain_num):
self.out_file = self.ramdump.open_file(
'msm_iommu_domain_%02d.txt' % (d.domain_num))
'msm_iommu_domain_%02d.txt' % (domain_num))
redirect = 'OFF'
if d.redirect is None:
redirect = 'UNKNOWN'
......@@ -318,8 +320,9 @@ class IOMMU(RamParser):
iommu_context += '%s (%d) ' % (name, num)
iommu_context = iommu_context.strip()
self.out_file.write('IOMMU Context: %s. Domain: %s (%d) [L2 cache redirect for page tables is %s]\n' % (
iommu_context, d.client_name, d.domain_num, redirect))
self.out_file.write('IOMMU Context: %s. Domain: %s'
'[L2 cache redirect for page tables is %s]\n' % (
iommu_context, d.client_name, redirect))
self.out_file.write(
'[VA Start -- VA End ] [Size ] [PA Start -- PA End ] [Size ] [Read/Write][Page Table Entry Size]\n')
if d.pg_table == 0:
......@@ -339,8 +342,14 @@ class IOMMU(RamParser):
'[!] WARNING: IOMMU domains was not found in this build. No IOMMU page tables will be generated')
return
for d in self.domain_list:
for (domain_num, d) in enumerate(self.domain_list):
if self.ramdump.is_config_defined('CONFIG_IOMMU_LPAE'):
parse_long_form_tables(self.ramdump, d)
parse_long_form_tables(self.ramdump, d, domain_num)
elif (self.ramdump.is_config_defined('CONFIG_IOMMU_AARCH64') or
self.ramdump.is_config_defined('CONFIG_ARM_SMMU')):
if (d.domain_type == MSM_SMMU_DOMAIN):
self.parse_short_form_tables(d, domain_num)
else:
parse_aarch64_tables(self.ramdump, d, domain_num)
else:
self.parse_short_form_tables(d)
self.parse_short_form_tables(d, domain_num)
......@@ -67,6 +67,7 @@ SZ_512M = 0x20000000
SZ_1G = 0x40000000
SZ_2G = 0x80000000
SZ_256G = 0x8000000000
size_strings = OrderedDict((
(SZ_1, "1"),
......@@ -101,6 +102,7 @@ size_strings = OrderedDict((
(SZ_512M, "512M"),
(SZ_1G, "1G"),
(SZ_2G, "2G"),
(SZ_256G, "256G"),
))
def is_power_of_two(n):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment