diff --git a/misc/config_tools/board_inspector/acpiparser/aml/context.py b/misc/config_tools/board_inspector/acpiparser/aml/context.py index acb943cd5..72ba78701 100644 --- a/misc/config_tools/board_inspector/acpiparser/aml/context.py +++ b/misc/config_tools/board_inspector/acpiparser/aml/context.py @@ -32,17 +32,27 @@ class OperationFieldDecl(NamedDecl): self.region = None self.offset = None self.length = length + self.access_width = None - def set_location(self, region, offset): + def set_location(self, region, offset, access_width): self.region = region self.offset = offset + self.access_width = access_width + + def set_indexed_location(self, index_register, data_register, index, access_width): + self.region = (index_register, data_register) + self.offset = index + self.access_width = access_width def dump(self): if self.region and self.offset: bit_index = self.offset byte_index = floor(bit_index / 8) offset_in_byte = bit_index % 8 - print(f"{self.name}: {self.__class__.__name__}, {self.region}: bit {hex(bit_index)} (byte {hex(byte_index)}.{offset_in_byte}), {self.length} bits") + if isinstance(self.region, str): + print(f"{self.name}: {self.__class__.__name__}, {self.region}: bit {hex(bit_index)} (byte {hex(byte_index)}.{offset_in_byte}), {self.length} bits") + else: + print(f"{self.name}: {self.__class__.__name__}, ({self.region[0]}, {self.region[1]}): bit {hex(bit_index)} (byte {hex(byte_index)}.{offset_in_byte}), {self.length} bits") else: print(f"{self.name}: {self.__class__.__name__}, {self.length} bits") diff --git a/misc/config_tools/board_inspector/acpiparser/aml/datatypes.py b/misc/config_tools/board_inspector/acpiparser/aml/datatypes.py index 4aef181dd..9fa4a2378 100644 --- a/misc/config_tools/board_inspector/acpiparser/aml/datatypes.py +++ b/misc/config_tools/board_inspector/acpiparser/aml/datatypes.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: BSD-3-Clause # +import sys import mmap import logging from math import floor, ceil @@ -38,52 +39,53 @@ class UninitializedObject(Object): def to_string(self): return "Uninitialized Object" -class Buffer(Object): +class BufferBase(Object): @staticmethod def bitmask(to, frm): return ((1 << (to + 1)) - 1) - ((1 << frm) - 1) - def __init__(self, data): - assert len(data) > 0 - self.__data = bytearray(data) - self.__fields = {} # name -> (offset, bitwidth) + def __init__(self, length): + self.__length = length + self.__fields = {} # name -> (offset, bitwidth, access_width) - @property - def data(self): - return bytes(self.__data) + def read(self, norm_idx, bit_width): + return NotImplementedError(self.__class__.__name__) - def create_field(self, name, offset, bitwidth): - self.__fields[name] = (offset, bitwidth) + def write(self, norm_idx, value, bit_width): + return NotImplementedError(self.__class__.__name__) + + def create_field(self, name, offset, bitwidth, access_width): + self.__fields[name] = (offset, bitwidth, access_width) def read_field(self, name): - offset, bitwidth = self.__fields[name] + offset, bitwidth, access_width = self.__fields[name] acc = 0 acc_bit_count = 0 bit_idx = offset bit_remaining = bitwidth - assert offset + bitwidth <= len(self.__data) * 8, \ + assert offset + bitwidth <= self.__length * 8, \ f"Buffer overflow: attempt to access field {name} at bit {offset + bitwidth} while the buffer has only {len(self.__data) * 8} bits" # Bits out of byte boundary - if bit_idx % 8 > 0: - byte_idx = floor(bit_idx / 8) - bit_count = (8 - bit_idx % 8) + if bit_idx % access_width > 0: + norm_idx = floor(bit_idx / access_width) + bit_count = (access_width - bit_idx % access_width) if bit_count > bit_remaining: bit_count = bit_remaining - mask = self.bitmask(bit_idx % 8 + bit_count - 1, bit_idx % 8) - acc = (self.__data[byte_idx] & mask) >> bit_idx % 8 + mask = self.bitmask(bit_idx % access_width + bit_count - 1, bit_idx % access_width) + acc = (self.read(norm_idx, access_width) & mask) >> (bit_idx % access_width) acc_bit_count += bit_count bit_idx += bit_count bit_remaining -= bit_count while bit_remaining > 0: - byte_idx = floor(bit_idx / 8) - bit_count = 8 if bit_remaining >= 8 else bit_remaining + norm_idx = floor(bit_idx / access_width) + bit_count = access_width if bit_remaining >= access_width else bit_remaining mask = self.bitmask(bit_count - 1, 0) - acc |= (self.__data[byte_idx] & mask) << acc_bit_count + acc |= (self.read(norm_idx, access_width) & mask) << acc_bit_count acc_bit_count += bit_count bit_idx += bit_count bit_remaining -= bit_count @@ -91,46 +93,69 @@ class Buffer(Object): return acc def write_field(self, name, value): - offset, bitwidth = self.__fields[name] + offset, bitwidth, access_width = self.__fields[name] bit_idx = offset bit_remaining = bitwidth - assert offset + bitwidth <= len(self.__data) * 8, \ + assert offset + bitwidth <= self.__length * 8, \ f"Buffer overflow: attempt to access field {name} at bit {offset + bitwidth} while the buffer has only {len(self.__data) * 8} bits" # Bits out of byte boundary - if bit_idx % 8 > 0: - byte_idx = floor(bit_idx / 8) - bit_count = (8 - bit_idx % 8) + if bit_idx % access_width > 0: + norm_idx = floor(bit_idx / access_width) + bit_count = (access_width - bit_idx % access_width) if bit_count > bit_remaining: bit_count = bit_remaining - mask = self.bitmask(bit_idx % 8 + bit_count - 1, bit_idx % 8) - v = (value & ((1 << bit_count) - 1)) << (bit_idx % 8) - self.__data[byte_idx] = (v & mask) | (self.__data[byte_idx] & (0xFF - mask)) + mask_of_write = self.bitmask(bit_idx % access_width + bit_count - 1, bit_idx % access_width) + mask_of_keep = ((1 << access_width) - 1) - mask_of_write + v = (value & ((1 << bit_count) - 1)) << (bit_idx % access_width) + self.write(norm_idx, (v & mask_of_write) | (self.read(norm_idx, access_width) & mask_of_keep), access_width) value >>= bit_count bit_idx += bit_count bit_remaining -= bit_count while bit_remaining > 0: - byte_idx = floor(bit_idx / 8) - bit_count = 8 if bit_remaining >= 8 else bit_remaining + norm_idx = floor(bit_idx / access_width) + bit_count = access_width if bit_remaining >= access_width else bit_remaining - mask = self.bitmask(bit_count - 1, 0) + mask_of_write = self.bitmask(bit_count - 1, 0) + mask_of_keep = ((1 << access_width) - 1) - mask_of_write v = (value & ((1 << bit_count) - 1)) - self.__data[byte_idx] = (v & mask) | (self.__data[byte_idx] & (0xFF - mask)) + self.write(norm_idx, (v & mask_of_write) | (self.read(norm_idx, access_width) & mask_of_keep), access_width) value >>= bit_count bit_idx += bit_count bit_remaining -= bit_count - def get(self): - return self.__data - def to_buffer(self): return self +class Buffer(BufferBase): + def __init__(self, data): + assert len(data) > 0 + super().__init__(len(data)) + self.__data = bytearray(data) + + @property + def data(self): + return bytes(self.__data) + + def read(self, norm_idx, bit_width): + acc = 0 + byte_width = bit_width // 8 + offset = norm_idx * byte_width + return int.from_bytes(self.__data[offset : (offset + byte_width)], sys.byteorder) + + def write(self, norm_idx, value, bit_width): + byte_width = bit_width // 8 + offset = norm_idx * byte_width + self.__data[offset : (offset + byte_width)] = value.to_bytes(byte_width, sys.byteorder) + + def get(self): + return self.__data + def to_hex_string(self): result = ",".join(map(lambda x:hex(x)[2:], self.__data)) return String(result) @@ -144,6 +169,41 @@ class Buffer(Object): i -= 1 return Integer(acc) +class StreamIOBuffer(BufferBase): + def __init__(self, stream, base, length): + super().__init__(length) + self.__stream = stream + self.__base = base + + def read(self, norm_idx, bit_width): + byte_width = bit_width // 8 + address = norm_idx * byte_width + self.__stream.seek(self.__base + address) + data = self.__stream.read(byte_width) + return int.from_bytes(data, sys.byteorder) + + def write(self, norm_idx, value, bit_width): + byte_width = bit_width // 8 + address = norm_idx * byte_width + self.__stream.seek(self.__base + address) + self.__stream.write(value.to_bytes(byte_width, sys.byteorder)) + +class IndexedIOBuffer(BufferBase): + def __init__(self, index_register, data_register): + # FIXME: Get the real size of an indexed I/O region + super().__init__(256) + self.__index_register = index_register + self.__data_register = data_register + + def read(self, norm_idx, bit_width): + assert bit_width == 8, f"Indexed I/O buffers can only be read one byte at a time" + self.__index_register.set(Integer(norm_idx, 8)) + return self.__data_register.get() + + def write(self, norm_idx, value, bit_width): + # Do not allow writes to indexed I/O buffer + assert False, "Cannot write to indexed I/O buffers" + class BufferField(Object): def __init__(self, buf, field): self.__buf = buf @@ -155,11 +215,14 @@ class BufferField(Object): def set(self, obj): self.__buf.write_field(self.__field, obj.get()) + def set_writable(self): + self.__buf.set_field_writable(self.__field) + def to_integer(self): return Integer(self.get()) def to_string(self): - return "Buffer Field" + return f"BufferField({self.__field})" # DebugObject @@ -237,45 +300,85 @@ class ObjectReference(Object): self.__obj = obj self.__index = index -class OperationRegion(Buffer): - def __load_system_memory_space(self, offset, length): +class OperationRegion(Object): + devmem = None + devport = None + opened_indexed_regions = {} + + @classmethod + def open_system_memory(cls, name, offset, length): + if not cls.devmem: + cls.devmem = open("/dev/mem", "rb", buffering=0) + + logging.info(f"Open system memory space {name}: [{hex(offset)}, {hex(offset + length - 1)}]") offset_page_aligned = (offset >> 12) << 12 length_page_aligned = ceil(((offset & 0xFFF) + length) / 0x1000) * 0x1000 + mm = mmap.mmap(cls.devmem.fileno(), length_page_aligned, flags=mmap.MAP_PRIVATE, prot=mmap.PROT_READ, offset=offset_page_aligned) + iobuf = StreamIOBuffer(mm, offset & 0xFFF, length) + return OperationRegion(iobuf) - with open('/dev/mem', 'rb') as f: - mm = mmap.mmap(f.fileno(), length_page_aligned, flags=mmap.MAP_PRIVATE, prot=mmap.PROT_READ, offset=offset_page_aligned) - mm.seek(offset & 0xFFF) - data = mm.read(length) - super().__init__(data) + @classmethod + def open_system_io(cls, name, offset, length): + if not cls.devport: + cls.devport = open("/dev/port", "w+b", buffering=0) - def __load_pci_configuration_space(self, bus, device, function, offset, length): + logging.info(f"Open system I/O space {name}: [{hex(offset)}, {hex(offset + length - 1)}]") + iobuf = StreamIOBuffer(cls.devport, offset, length) + return OperationRegion(iobuf) + + @classmethod + def open_pci_configuration_space(cls, bus_number, device_adr, offset, length): + assert offset <= 0xFF and (offset + length) <= 0x100 + # Assume bus is 0 for now + bus = bus_number + device = device_adr >> 16 + function = device_adr & 0xFF sysfs_path = "/sys/devices/pci0000:%02x/0000:%02x:%02x.%d/config" % (bus, bus, device, function) try: - with open(sysfs_path, "rb") as f: - f.seek(offset) - data = f.read(length) - super().__init__(data) + f = open(sysfs_path, "rb", buffering=0) + iobuf = StreamIOBuffer(f, offset, length) + return OperationRegion(iobuf) except FileNotFoundError: - logging.error(f"Cannot read the configuration space of %02x:%02x.%d from {sysfs_path}. Assume the PCI device does not exist." % (bus, device, function)) + logging.warning(f"Cannot read the configuration space of %02x:%02x.%d from {sysfs_path}. Assume the PCI device does not exist." % (bus, device, function)) data = bytearray([0xff]) * length - super().__init__(data) + buf = Buffer(data) + return OperationRegion(buf) - def __init__(self, bus_id, device_id, name, space, offset, length): - if space == 0x00: # SystemMemory - logging.info(f"Loading system memory space {name}: [{hex(offset)}, {hex(offset + length - 1)}]") - self.__load_system_memory_space(offset, length) - elif space == 0x01: # SystemIO - raise FutureWork("Port I/O operation region") - elif space == 0x02: # PCI_Config - assert offset <= 0xFF and (offset + length) <= 0x100 - # Assume bus is 0 for now - bus = bus_id - device = device_id >> 16 - function = device_id & 0xFF - logging.info(f"Loading PCI configuration space {name}: 00:%02x:%d [{hex(offset)}, {hex(offset + length - 1)}]" % (device, function)) - self.__load_pci_configuration_space(bus, device, function, offset, length) + @classmethod + def open_indexed_region(cls, index_register, data_register): + logging.info(f"Open I/O region indexed by index register {index_register.to_string()} and data register {data_register.to_string()}.") + k = (str(index_register), str(data_register)) + if k not in cls.opened_indexed_regions.keys(): + iobuf = IndexedIOBuffer(index_register, data_register) + region = OperationRegion(iobuf) + cls.opened_indexed_regions[k] = region + + # Mark the index register as writable + index_register.set_writable() + + return region else: - raise NotImplementedError(f"Cannot load operation region in space {space}") + return cls.opened_indexed_regions[k] + + def __init__(self, iobuf): + self.__iobuf = iobuf + self.__writable_fields = set() + + def create_field(self, name, offset, bitwidth, access_width): + self.__iobuf.create_field(name, offset, bitwidth, access_width) + + def read_field(self, name): + return self.__iobuf.read_field(name) + + def write_field(self, name, value): + # Do not allow writes to stream I/O buffer unless the base is explicitly marked as writable + if name in self.__writable_fields: + self.__iobuf.write_field(name, value) + else: + logging.info(f"Skip writing 0x{value:0X} to I/O field {name}") + + def set_field_writable(self, name): + self.__writable_fields.add(name) def to_string(self): return "Operation Region" diff --git a/misc/config_tools/board_inspector/acpiparser/aml/interpreter.py b/misc/config_tools/board_inspector/acpiparser/aml/interpreter.py index a3a53b4e5..d78e080e9 100644 --- a/misc/config_tools/board_inspector/acpiparser/aml/interpreter.py +++ b/misc/config_tools/board_inspector/acpiparser/aml/interpreter.py @@ -233,11 +233,20 @@ class ConcreteInterpreter(Interpreter): sym = self.context.lookup_symbol(self.context.realpath(tree.scope, name)) assert isinstance(sym, OperationFieldDecl) assert sym.region, f"Field {sym.name} does not belong to any operation region." - buf = self.context.lookup_operation_region(sym.region) - if not buf: - buf = self.interpret(self.context.lookup_symbol(sym.region).tree) - buf.create_field(name, sym.offset, sym.length) - field = BufferField(buf, name) + if isinstance(sym.region, str): + buf = self.context.lookup_operation_region(sym.region) + if not buf: + buf = self.interpret(self.context.lookup_symbol(sym.region).tree) + buf.create_field(name, sym.offset, sym.length, sym.access_width) + field = BufferField(buf, name) + elif isinstance(sym.region, tuple): + index_register = self.interpret(sym.region[0].tree) + data_register = self.interpret(sym.region[1].tree) + buf = OperationRegion.open_indexed_region(index_register, data_register) + buf.create_field(name, sym.offset, sym.length, sym.access_width) + field = BufferField(buf, name) + else: + assert False, f"Cannot interpret the operation region: {sym.region}" return field def create_field(self, tree, bitwidth, name_idx): @@ -246,9 +255,10 @@ class ConcreteInterpreter(Interpreter): index = self.interpret(tree.children[1]).get() name = tree.children[name_idx].children if bitwidth == 1 or name_idx == 3: - buf.create_field(name, index, bitwidth) + buf.create_field(name, index, bitwidth, 8) else: - buf.create_field(name, index * 8, bitwidth) + # bitwidth is 8, 16, 32 or 64 in this case. Reuse it as the access width. + buf.create_field(name, index * 8, bitwidth, bitwidth) obj = BufferField(buf, name) self.context.register_binding(name, obj) return obj @@ -298,18 +308,21 @@ class ConcreteInterpreter(Interpreter): length = self.interpret(tree.children[3]).get() assert isinstance(space, int) and isinstance(offset, int) and (length, int) - # For PCI configuration space, try to invoke _ADR (if any) of the device containing the region to get the device - # identifier - bus_id = None - device_id = None - if space == 0x2: # PCI_Config + if space == 0x00: # SystemMemory + op_region = OperationRegion.open_system_memory(sym.name, offset, length) + elif space == 0x01: # SystemIO + op_region = OperationRegion.open_system_io(sym.name, offset, length) + elif space == 0x02: # PCI_Config self.context.change_scope(tree.scope) device_path = self.context.parent(sym.name) bus_id = self.interpret_method_call(f"_BBN").get() device_id = self.interpret_method_call(f"{device_path}._ADR").get() self.context.pop_scope() + op_region = OperationRegion.open_pci_configuration_space(bus_id, device_id, offset, length) + pass + else: + raise NotImplementedError(f"Cannot load operation region in space {space}") - op_region = OperationRegion(bus_id, device_id, sym.name, space, offset, length) self.context.register_operation_region(sym.name, op_region) return op_region diff --git a/misc/config_tools/board_inspector/acpiparser/aml/parser.py b/misc/config_tools/board_inspector/acpiparser/aml/parser.py index c1a65bf98..b05034350 100644 --- a/misc/config_tools/board_inspector/acpiparser/aml/parser.py +++ b/misc/config_tools/board_inspector/acpiparser/aml/parser.py @@ -459,9 +459,21 @@ def DefExternal_hook_post(context, tree): sym = NamedDecl(name, tree) context.register_symbol(sym) +access_width_map = { + 0: 8, # AnyAcc + 1: 8, # ByteAcc + 2: 16, # WordAcc + 3: 32, # DWordAcc + 4: 64, # QWordAcc + 5: 8, # BufferAcc + # The other values are reserved +} + def DefField_hook_post(context, tree): # Update the fields with region & offset info region_name = context.lookup_symbol(tree.children[1].children).name + flags = tree.children[2].children[0].children + access_width = access_width_map[flags & 0xF] fields = tree.children[3].children bit_offset = 0 for field in fields: @@ -471,7 +483,30 @@ def DefField_hook_post(context, tree): length = field.children[1].children sym = context.lookup_symbol(name) assert isinstance(sym, OperationFieldDecl) - sym.set_location(region_name, bit_offset) + sym.set_location(region_name, bit_offset, access_width) + bit_offset += length + elif field.label == "ReservedField": + length = field.children[0].children + bit_offset += length + else: + break + +def DefIndexField_hook_post(context, tree): + # Update the fields with region & offset info + index_register = context.lookup_symbol(tree.children[1].children) + data_register = context.lookup_symbol(tree.children[2].children) + flags = tree.children[3].children[0].children + access_width = access_width_map[flags & 0xF] + fields = tree.children[4].children + bit_offset = 0 + for field in fields: + field = field.children[0] + if field.label == "NamedField": + name = field.children[0].children + length = field.children[1].children + sym = context.lookup_symbol(name) + assert isinstance(sym, OperationFieldDecl) + sym.set_indexed_location(index_register, data_register, bit_offset, access_width) bit_offset += length elif field.label == "ReservedField": length = field.children[0].children