mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
93c2a23d7c
Original commit for chipsec v1: 60ab31da9d
Signed-off-by: Nathaniel Mitchell <nathaniel.p.mitchell@intel.com>
397 lines
17 KiB
Python
397 lines
17 KiB
Python
# CHIPSEC: Platform Security Assessment Framework
|
|
# Copyright (c) 2010-2021, Intel Corporation
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; Version 2.
|
|
#
|
|
# 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.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# Contact information:
|
|
# chipsec@intel.com
|
|
#
|
|
|
|
"""
|
|
Access to MMIO (Memory Mapped IO) BARs and Memory-Mapped PCI Configuration Space (MMCFG)
|
|
|
|
usage:
|
|
>>> read_MMIO_reg(bar_base, 0x0, 4)
|
|
>>> write_MMIO_reg(bar_base, 0x0, 0xFFFFFFFF, 4)
|
|
>>> read_MMIO(bar_base, 0x1000)
|
|
>>> dump_MMIO(bar_base, 0x1000)
|
|
|
|
Access MMIO by BAR name:
|
|
|
|
>>> read_MMIO_BAR_reg('MCHBAR', 0x0, 4)
|
|
>>> write_MMIO_BAR_reg('MCHBAR', 0x0, 0xFFFFFFFF, 4)
|
|
>>> get_MMIO_BAR_base_address('8086.HOSTCTL.MCHBAR')
|
|
>>> is_MMIO_BAR_enabled('8086.HOSTCTL.MCHBAR')
|
|
>>> is_MMIO_BAR_programmed('8086.HOSTCTL.MCHBAR')
|
|
>>> dump_MMIO_BAR('8086.HOSTCTL.MCHBAR')
|
|
>>> list_MMIO_BARs()
|
|
|
|
"""
|
|
from typing import List, Optional, Tuple
|
|
from chipsec.hal import hal_base
|
|
from chipsec.library.exceptions import CSConfigError, CSReadError, MMIOBARNotFoundError
|
|
from chipsec.library.options import Options
|
|
|
|
DEFAULT_MMIO_BAR_SIZE = 0x1000
|
|
|
|
|
|
class MMIO(hal_base.HALBase):
|
|
|
|
def __init__(self, cs):
|
|
super(MMIO, self).__init__(cs)
|
|
self.cached_bar_addresses = {}
|
|
options = Options()
|
|
self.cache_bar_addresses_resolution = options.get_section_data('HAL_Config', 'mmio_cache_bar_addresses') == "True"
|
|
|
|
###########################################################################
|
|
# Access to MMIO BAR defined by configuration files (chipsec/cfg/*.py)
|
|
###########################################################################
|
|
#
|
|
# To add your own MMIO bar:
|
|
# 1. Add new MMIO BAR id (any)
|
|
# 2. Write a function get_yourBAR_base_address() with no args that
|
|
# returns base address of new bar
|
|
# 3. Add a pointer to this function to MMIO_BAR_base map
|
|
# 4. Don't touch read/write_MMIO_reg functions ;)
|
|
#
|
|
###########################################################################
|
|
|
|
#
|
|
# Read MMIO register as an offset off of MMIO range base address
|
|
#
|
|
|
|
def read_MMIO_reg(self, bar_base: int, offset: int, size: int = 4, bar_size: Optional[int] = None) -> int:
|
|
if size > 8:
|
|
if self.logger.HAL:
|
|
self.logger.log_warning("[mmio] MMIO read cannot exceed 8")
|
|
if bar_size and offset + size > bar_size:
|
|
self.logger.log_warning(f"[mmio] Offset(0x{offset:x}) + size(0x{size:x}) is > bar_size(0x{bar_size:x})")
|
|
reg_value = self.cs.helper.read_mmio_reg(bar_base+offset, size)
|
|
self.logger.log_hal(f'[mmio] 0x{bar_base:08X} + 0x{offset:08X} = 0x{reg_value:08X}')
|
|
return reg_value
|
|
|
|
def read_MMIO_reg_byte(self, bar_base: int, offset: int) -> int:
|
|
return self.read_MMIO_reg(bar_base, offset, 1)
|
|
|
|
def read_MMIO_reg_word(self, bar_base: int, offset: int) -> int:
|
|
return self.read_MMIO_reg(bar_base, offset, 2)
|
|
|
|
def read_MMIO_reg_dword(self, bar_base: int, offset: int) -> int:
|
|
return self.read_MMIO_reg(bar_base, offset, 4)
|
|
|
|
#
|
|
# Write MMIO register as an offset off of MMIO range base address
|
|
#
|
|
def write_MMIO_reg(self, bar_base: int, offset: int, value: int, size: int = 4) -> int:
|
|
address = bar_base + offset
|
|
self.logger.log_hal(f'[mmio] write 0x{bar_base:08X} + 0x{offset:08X} = 0x{value:08X}')
|
|
return self.cs.helper.write_mmio_reg(address, size, value)
|
|
|
|
def write_MMIO_reg_byte(self, bar_base: int, offset: int, value: int) -> int:
|
|
address = bar_base + offset
|
|
self.logger.log_hal(f'[mmio] write 0x{bar_base:08X} + 0x{offset:08X} = 0x{value:08X}')
|
|
return self.cs.helper.write_mmio_reg(address, 1, value)
|
|
|
|
def write_MMIO_reg_word(self, bar_base: int, offset: int, value: int) -> int:
|
|
address = bar_base + offset
|
|
self.logger.log_hal(f'[mmio] write 0x{bar_base:08X} + 0x{offset:08X} = 0x{value:08X}')
|
|
return self.cs.helper.write_mmio_reg(address, 2, value)
|
|
|
|
def write_MMIO_reg_dword(self, bar_base: int, offset: int, value: int) -> int:
|
|
address = bar_base + offset
|
|
self.logger.log_hal(f'[mmio] write 0x{bar_base:08X} + 0x{offset:08X} = 0x{value:08X}')
|
|
return self.cs.helper.write_mmio_reg(address, 4, value)
|
|
|
|
#
|
|
# Read MMIO registers as offsets off of MMIO range base address
|
|
#
|
|
def read_MMIO(self, bar_base: int, size: int) -> List[int]:
|
|
regs = []
|
|
size -= size % 4
|
|
for offset in range(0, size, 4):
|
|
regs.append(self.read_MMIO_reg(bar_base, offset))
|
|
return regs
|
|
|
|
#
|
|
# Dump MMIO range
|
|
#
|
|
def dump_MMIO(self, bar_base: int, size: int) -> None:
|
|
self.logger.log(f'[mmio] MMIO register range [0x{bar_base:016X}:0x{bar_base:016X}+{size:08X}]:')
|
|
size -= size % 4
|
|
for offset in range(0, size, 4):
|
|
self.logger.log(f'+{offset:08X}: {self.read_MMIO_reg(bar_base, offset):08X}')
|
|
|
|
###############################################################################
|
|
# Access to MMIO BAR defined by XML configuration files (chipsec/cfg/*.xml)
|
|
###############################################################################
|
|
|
|
#
|
|
# Check if MMIO BAR with bar_name has been defined in XML config
|
|
# Use this function to fall-back to hardcoded config in case XML config is not available
|
|
#
|
|
|
|
def is_MMIO_BAR_defined(self, bar_name: str) -> bool:
|
|
is_bar_defined = False
|
|
try:
|
|
_bar = self.cs.register.mmio.get_def(bar_name)
|
|
if _bar is not None:
|
|
if _bar.register:
|
|
is_bar_defined = self.cs.register.is_defined(_bar.register)
|
|
if not is_bar_defined and _bar.fixed_address:
|
|
is_bar_defined = True
|
|
except KeyError:
|
|
pass
|
|
|
|
if not is_bar_defined:
|
|
if self.logger.HAL:
|
|
self.logger.log_warning(f"'{bar_name}' MMIO BAR definition not found/correct in XML config")
|
|
return is_bar_defined
|
|
|
|
#
|
|
# Enable caching of BAR addresses
|
|
#
|
|
def enable_cache_address_resolution(self, enable: bool) -> None:
|
|
if enable:
|
|
self.cache_bar_addresses_resolution = True
|
|
else:
|
|
self.cache_bar_addresses_resolution = False
|
|
self.flush_bar_address_cache()
|
|
|
|
def flush_bar_address_cache(self) -> None:
|
|
self.cached_bar_addresses = {}
|
|
|
|
#
|
|
# Get base address of MMIO range by MMIO BAR name
|
|
#
|
|
def get_MMIO_BAR_base_address(self, bar_name: str, instance: Optional['PCIObj'] = None) -> Tuple[int, int]:
|
|
if self.cache_bar_addresses_resolution and (bar_name, instance) in self.cached_bar_addresses:
|
|
return self.cached_bar_addresses[(bar_name, instance)]
|
|
try:
|
|
bar = self.cs.register.mmio.get_def(bar_name) # self.cs.Cfg.MMIO_BARS[bar_name]
|
|
except KeyError:
|
|
raise MMIOBARNotFoundError(f'MMIOBARNotFound: {bar_name} is not defined. Check scoping and configuration')
|
|
if not bar:
|
|
raise MMIOBARNotFoundError(f'MMIOBARNotFound: {bar_name} is not defined. Check scoping and configuration')
|
|
base, size = bar.get_base(instance)
|
|
if base:
|
|
return base, size
|
|
base = 0
|
|
reg_mask = 0xFFFF
|
|
limit = 0
|
|
size = 0
|
|
mmioaddr = 0
|
|
|
|
if bar.register:
|
|
preserve = True
|
|
if instance is not None:
|
|
bar_reg_list = [self.cs.register.get_instance_by_name(bar.register, instance)]
|
|
else:
|
|
bar_reg_list = self.cs.register.get_list_by_name(bar.register)
|
|
for bar_reg in bar_reg_list:
|
|
if bar_reg is None:
|
|
raise MMIOBARNotFoundError(f'MMIOBARNotFound: {bar_name} is not defined. Check scoping and configuration')
|
|
if bar.reg_align:
|
|
preserve = False
|
|
if bar.base_field:
|
|
base_field = bar.base_field
|
|
try:
|
|
base = bar_reg.read_field(base_field, preserve)
|
|
except (CSReadError, AttributeError):
|
|
continue
|
|
try:
|
|
reg_mask = bar_reg.get_field_mask(base_field, preserve)
|
|
except CSReadError:
|
|
continue
|
|
break
|
|
if not preserve:
|
|
base <<= bar.reg_align
|
|
reg_mask <<= bar.reg_align
|
|
if bar.registerh and instance is not None:
|
|
preserve = True
|
|
bar_reg = self.cs.register.get_instance_by_name(bar.registerh, instance)
|
|
if bar_reg:
|
|
if bar.regh_align:
|
|
preserve = False
|
|
if bar.baseh_field:
|
|
base_field = bar.baseh_field
|
|
try:
|
|
baseh = bar_reg.read_field(base_field, preserve)
|
|
except CSReadError:
|
|
self.logger.log_hal('[mmio] Unable to determine MMIO Base registerh. Using Base = 0x0')
|
|
baseh = 0
|
|
try:
|
|
reg_maskh = bar_reg.get_field_mask(base_field, preserve)
|
|
except CSReadError:
|
|
self.logger.log_hal('[mmio] Unable to determine MMIO Mask registerh. Using Mask = 0xFFFF')
|
|
reg_maskh = 0xFFFF
|
|
if not preserve:
|
|
baseh <<= bar.reg_align
|
|
reg_maskh <<= bar.reg_align
|
|
base += baseh
|
|
reg_mask += reg_maskh
|
|
if bar.registertype and bar.registertype == 'dynamic':
|
|
try:
|
|
dynbase = self.read_MMIO_reg(base, 0)
|
|
except CSReadError:
|
|
self.logger.log_hal('[mmio] Unable to determine MMIO Base. Using Base = 0x0')
|
|
dynbase = 0x0
|
|
base = dynbase
|
|
if bar.mmio_base:
|
|
mmiobar = bar.mmio_base
|
|
mmioaddr, _ = self.get_MMIO_BAR_base_address(mmiobar, instance)
|
|
if bar.mmio_align:
|
|
mmioaddr <<= bar.mmio_align
|
|
base += mmioaddr
|
|
|
|
if bar.limit_register and bar.limit_field and bar.limit_align and instance is not None:
|
|
limit_field = bar.limit_field
|
|
limit_bar = bar.limit_register
|
|
lim_reg = self.cs.register.get_instance_by_name(limit_bar, instance)
|
|
limit = lim_reg.read_field(limit_field)
|
|
if bar.limit_align:
|
|
limit_align = bar.limit_align
|
|
limit <<= limit_align
|
|
|
|
if bar.fixed_address and (base == reg_mask or base == 0):
|
|
base = bar.fixed_address
|
|
self.logger.log_hal('[mmio] Using fixed address for {}: 0x{:016X}'.format(bar_name, base))
|
|
if bar.offset:
|
|
base += bar.offset
|
|
if bar.size:
|
|
size = bar.size
|
|
elif limit:
|
|
if bar.mmio_align:
|
|
limit += ((0x1 << bar['mmio_align']) - 1)
|
|
limit += mmioaddr
|
|
size = limit - base
|
|
if size == 0:
|
|
size = DEFAULT_MMIO_BAR_SIZE
|
|
self.logger.log_hal('[mmio] {}: 0x{:016X} (size = 0x{:X})'.format(bar_name, base, size))
|
|
if base == 0 and bar.fixed_address is None:
|
|
self.logger.log_hal('[mmio] Base address was determined to be 0.')
|
|
raise CSReadError('[mmio] Base address was determined to be 0')
|
|
|
|
if self.cache_bar_addresses_resolution:
|
|
self.cached_bar_addresses[(bar_name, instance)] = (base, size)
|
|
bar.update_base_address(base, instance)
|
|
return base, size
|
|
|
|
#
|
|
# Check if MMIO range is enabled by MMIO BAR name
|
|
#
|
|
def is_MMIO_BAR_enabled(self, bar_name: str, instance: Optional['PCIObj'] = None) -> bool:
|
|
if not self.is_MMIO_BAR_defined(bar_name):
|
|
return False
|
|
bar = self.cs.register.mmio.get_def(bar_name)
|
|
is_enabled = True
|
|
if bar.register:
|
|
bar_reg = self.cs.register.get_list_by_name(bar.register).filter_by_instance(instance)
|
|
if bar.enable_field:
|
|
is_enabled = bar_reg.is_all_field_value(1, bar.enable_field)
|
|
elif bar.enable_bit:
|
|
base = bar_reg.read()[0]
|
|
en_mask = 1 << bar.enable_bit
|
|
is_enabled = (0 != base & en_mask)
|
|
else:
|
|
self.logger.log_hal(f'No enable field/bit defined for MMIO BAR {bar_name}')
|
|
else:
|
|
raise CSConfigError(f"MMIO BAR {bar_name} does not have a register defined")
|
|
|
|
return is_enabled
|
|
|
|
#
|
|
# Check if MMIO range is programmed by MMIO BAR name
|
|
#
|
|
def is_MMIO_BAR_programmed(self, bar_name: str) -> bool:
|
|
bar = self.cs.Cfg.MMIO_BARS[bar_name]
|
|
|
|
if bar.register:
|
|
bar_reg = self.cs.register.get_list_by_name(bar.register)
|
|
if bar.base_field:
|
|
return not bar_reg.is_any_field_value(0, bar.base_field)
|
|
else:
|
|
bar_reg.read()
|
|
return not bar_reg.is_any_value(0)
|
|
else:
|
|
raise CSConfigError(f"MMIO BAR {bar_name} does not have a register defined")
|
|
|
|
#
|
|
# Read MMIO register from MMIO range defined by MMIO BAR name
|
|
#
|
|
def read_MMIO_BAR_reg(self, bar_name: str, offset: int, size: int = 4, bus: Optional[int] = None) -> int:
|
|
(bar_base, bar_size) = self.get_MMIO_BAR_base_address(bar_name, bus)
|
|
# @TODO: check offset exceeds BAR size
|
|
return self.read_MMIO_reg(bar_base, offset, size, bar_size)
|
|
|
|
#
|
|
# Write MMIO register from MMIO range defined by MMIO BAR name
|
|
#
|
|
def write_MMIO_BAR_reg(self, bar_name: str, offset: int, value: int, size: int = 4, bus: Optional[int] = None) -> Optional[int]:
|
|
(bar_base, _) = self.get_MMIO_BAR_base_address(bar_name, bus)
|
|
# @TODO: check offset exceeds BAR size
|
|
|
|
return self.write_MMIO_reg(bar_base, offset, value, size)
|
|
|
|
def read_MMIO_BAR(self, bar_name: str, bus: Optional[int] = None) -> List[int]:
|
|
(bar_base, bar_size) = self.get_MMIO_BAR_base_address(bar_name, bus)
|
|
return self.read_MMIO(bar_base, bar_size)
|
|
|
|
#
|
|
# Dump MMIO range by MMIO BAR name
|
|
#
|
|
def dump_MMIO_BAR(self, bar_name: str) -> None:
|
|
(bar_base, bar_size) = self.get_MMIO_BAR_base_address(bar_name)
|
|
self.dump_MMIO(bar_base, bar_size)
|
|
|
|
def list_MMIO_BARs(self) -> None:
|
|
self.logger.log('')
|
|
self.logger.log('-' * 105)
|
|
self.logger.log(f' {"MMIO Range":35} | {"B:D.F":7} | {"Base":16} | {"Size":8} | En? | Val | Description')
|
|
self.logger.log('-' * 105)
|
|
for vid in self.cs.Cfg.MMIO_BARS:
|
|
for dev in self.cs.Cfg.MMIO_BARS[vid]:
|
|
for bar_name in self.cs.Cfg.MMIO_BARS[vid][dev]:
|
|
_bar_name = f'{vid}.{dev}.{bar_name}'
|
|
if not self.is_MMIO_BAR_defined(_bar_name):
|
|
continue
|
|
_bar = self.cs.Cfg.MMIO_BARS[vid][dev][bar_name]
|
|
for instance in _bar.instances:
|
|
(_base, _size) = _bar.get_base(instance)
|
|
if _base is None:
|
|
try:
|
|
(_base, _size) = self.get_MMIO_BAR_base_address(_bar_name, instance)
|
|
except CSReadError:
|
|
continue
|
|
_valid = self.is_MMIO_BAR_valid(_bar_name, instance)
|
|
_en = self.is_MMIO_BAR_enabled(_bar_name, instance)
|
|
if instance.bus is not None:
|
|
bdf = f'{instance.bus:02X}:{instance.dev:02X}.{instance.fun:1X}'
|
|
else:
|
|
bdf = 'fixed'
|
|
self.logger.log(f' {_bar_name:35} | {bdf:7} | {_base:016X} | {_size:08X} | {_en:d} | {_valid:d} | {_bar.desc}')
|
|
|
|
def is_MMIO_BAR_valid(self, bar_name, instance=None):
|
|
if not self.is_MMIO_BAR_defined(bar_name):
|
|
return False
|
|
bar = self.cs.register.mmio.get_def(bar_name)
|
|
is_valid = True
|
|
if bar.register:
|
|
if bar.valid:
|
|
bar_en_field = bar.valid
|
|
bar_reg = self.cs.register.get_list_by_name(bar.register).filter_by_instance(instance)
|
|
is_valid = bar_reg.is_all_field_value(1, bar_en_field)
|
|
return is_valid
|
|
|
|
haldata = {"arch": [hal_base.HALBase.MfgIds.Any], 'name': {'mmio': "MMIO"}}
|