Add a Python utility script for managing the TTMPageLimit EFI variable that controls GPU memory page limits in the TTM subsystem.
Assisted-by: Claude Opus 4.6 Signed-off-by: Mario Limonciello <[email protected]> --- tools/drm/ttm_efi_config.py | 303 ++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100755 tools/drm/ttm_efi_config.py diff --git a/tools/drm/ttm_efi_config.py b/tools/drm/ttm_efi_config.py new file mode 100755 index 0000000000000..374abd30de874 --- /dev/null +++ b/tools/drm/ttm_efi_config.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +TTM EFI Variable Configuration Utility + +This script allows users to read, write, and delete the TTMPageLimit EFI +variable, which controls the GPU memory page limit for the TTM subsystem. + +Usage: + sudo python3 ttm_efi_config.py --read + sudo python3 ttm_efi_config.py --set 2G + sudo python3 ttm_efi_config.py --set-pages 524288 + sudo python3 ttm_efi_config.py --delete +""" + +import argparse +import fcntl +import os +import struct +import sys + +# EFI variable configuration +GUID = "8be4df61-93ca-11ec-b909-0800200c9a66" +VAR_NAME = "TTMPageLimit" +EFIVARS_PATH = "/sys/firmware/efi/efivars" +VAR_FILE = f"{EFIVARS_PATH}/{VAR_NAME}-{GUID}" + +# Standard EFI variable attributes +# NON_VOLATILE | BOOTSERVICE_ACCESS | RUNTIME_ACCESS +EFI_ATTRS = 0x07 + +# System page size (typically 4KB) +PAGE_SIZE = os.sysconf("SC_PAGE_SIZE") + +# Linux ioctl constant for getting/setting file attributes +# From linux/fs.h +FS_IOC_GETFLAGS = 0x80086601 +FS_IOC_SETFLAGS = 0x40086602 +FS_IMMUTABLE_FL = 0x00000010 + + +def check_root(): + """Verify the script is running as root.""" + if os.geteuid() != 0: + print("Error: This script must be run as root", file=sys.stderr) + sys.exit(1) + + +def check_efi_available(): + """Check if EFI variables filesystem is available.""" + if not os.path.exists(EFIVARS_PATH): + print(f"Error: EFI variables filesystem not found at {EFIVARS_PATH}", + file=sys.stderr) + print("Ensure your system booted with EFI and the efivarfs is mounted.", + file=sys.stderr) + sys.exit(1) + + +def remove_immutable(filepath): + """ + Remove the immutable attribute from an EFI variable file. + + Args: + filepath: Path to the EFI variable file + + Returns: + True if successful, False otherwise + """ + try: + fd = os.open(filepath, os.O_RDONLY) + + flags = fcntl.ioctl(fd, FS_IOC_GETFLAGS, struct.pack('i', 0)) + flags_value = struct.unpack('i', flags)[0] + + if flags_value & FS_IMMUTABLE_FL: + new_flags = flags_value & ~FS_IMMUTABLE_FL + fcntl.ioctl(fd, FS_IOC_SETFLAGS, struct.pack('i', new_flags)) + + os.close(fd) + return True + except Exception as e: + print(f"Warning: Could not remove immutable attribute: {e}", file=sys.stderr) + return False + + +def parse_size(size_str): + """ + Parse a size string into page count. + + Args: + size_str: String like "2G", "512M", or raw page count + + Returns: + Number of pages (integer) + """ + size_str = size_str.strip().upper() + + multipliers = { + 'K': 1024, + 'M': 1024 * 1024, + 'G': 1024 * 1024 * 1024, + 'KB': 1024, + 'MB': 1024 * 1024, + 'GB': 1024 * 1024 * 1024, + } + + for suffix, multiplier in multipliers.items(): + if size_str.endswith(suffix): + try: + value = float(size_str[:-len(suffix)]) + bytes_total = int(value * multiplier) + return bytes_total // PAGE_SIZE + except ValueError: + print(f"Error: Invalid size format: {size_str}", file=sys.stderr) + sys.exit(1) + + try: + return int(size_str) + except ValueError: + print(f"Error: Invalid page count: {size_str}", file=sys.stderr) + sys.exit(1) + + +def format_pages(pages): + """ + Format page count as human-readable size. + + Args: + pages: Number of pages + + Returns: + Formatted string (e.g., "2.0 GB (524288 pages)") + """ + bytes_total = pages * PAGE_SIZE + + if bytes_total >= 1024 * 1024 * 1024: + size_gb = bytes_total / (1024 * 1024 * 1024) + return f"{size_gb:.1f} GB ({pages} pages)" + elif bytes_total >= 1024 * 1024: + size_mb = bytes_total / (1024 * 1024) + return f"{size_mb:.1f} MB ({pages} pages)" + elif bytes_total >= 1024: + size_kb = bytes_total / 1024 + return f"{size_kb:.1f} KB ({pages} pages)" + else: + return f"{bytes_total} bytes ({pages} pages)" + + +def read_efi_variable(): + """Read the TTMPageLimit EFI variable.""" + if not os.path.exists(VAR_FILE): + print(f"TTMPageLimit EFI variable is not set") + return + + try: + with open(VAR_FILE, 'rb') as f: + data = f.read() + + # EFI variable format: 4-byte attributes + data + if len(data) < 4: + print("Error: Invalid EFI variable (too short)", file=sys.stderr) + sys.exit(1) + + attrs = struct.unpack('<I', data[:4])[0] + value_data = data[4:] + + if len(value_data) != 8: + print(f"Warning: Variable size is {len(value_data)} bytes (expected 8)", + file=sys.stderr) + + if len(value_data) >= 8: + page_limit = struct.unpack('<Q', value_data[:8])[0] + print(f"TTMPageLimit: {format_pages(page_limit)}") + print(f"Attributes: 0x{attrs:02x}") + else: + print(f"Error: Variable data too short ({len(value_data)} bytes)", + file=sys.stderr) + sys.exit(1) + + except PermissionError: + print("Error: Permission denied. Run as root.", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error reading EFI variable: {e}", file=sys.stderr) + sys.exit(1) + + +def write_efi_variable(pages): + """ + Write the TTMPageLimit EFI variable. + + Args: + pages: Number of pages to set + """ + if pages <= 0: + print("Error: Page count must be positive", file=sys.stderr) + sys.exit(1) + + try: + with open('/proc/meminfo', 'r') as f: + for line in f: + if line.startswith('MemTotal:'): + mem_kb = int(line.split()[1]) + total_pages = (mem_kb * 1024) // PAGE_SIZE + if pages > total_pages: + print(f"Warning: Page count ({pages}) exceeds system RAM ({total_pages} pages)", + file=sys.stderr) + print("The kernel will cap this to system RAM.", file=sys.stderr) + break + except Exception as e: + print(f"Warning: Could not validate against system RAM: {e}", file=sys.stderr) + + attrs = struct.pack('<I', EFI_ATTRS) + value = struct.pack('<Q', pages) + data = attrs + value + + try: + if os.path.exists(VAR_FILE): + remove_immutable(VAR_FILE) + os.remove(VAR_FILE) + + fd = os.open(VAR_FILE, os.O_WRONLY | os.O_CREAT, 0o600) + os.write(fd, data) + os.close(fd) + + print(f"Successfully set TTMPageLimit to {format_pages(pages)}") + print("Reboot required for changes to take effect.") + + except PermissionError: + print("Error: Permission denied. Run as root and ensure efivarfs is writable.", + file=sys.stderr) + print("You may need to remount efivarfs with write access:", file=sys.stderr) + print(" mount -o remount,rw /sys/firmware/efi/efivars", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error writing EFI variable: {e}", file=sys.stderr) + sys.exit(1) + + +def delete_efi_variable(): + """Delete the TTMPageLimit EFI variable.""" + if not os.path.exists(VAR_FILE): + print("TTMPageLimit EFI variable is not set (nothing to delete)") + return + + try: + remove_immutable(VAR_FILE) + os.remove(VAR_FILE) + print("Successfully deleted TTMPageLimit EFI variable") + print("Reboot required for changes to take effect.") + except PermissionError: + print("Error: Permission denied. Run as root and ensure efivarfs is writable.", + file=sys.stderr) + print("You may need to remount efivarfs with write access:", file=sys.stderr) + print(" mount -o remount,rw /sys/firmware/efi/efivars", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error deleting EFI variable: {e}", file=sys.stderr) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser( + description='Manage TTM EFI page limit configuration', + epilog=''' +Examples: + %(prog)s --read # Read current value + %(prog)s --set 2G # Set to 2 GB + %(prog)s --set 512M # Set to 512 MB + %(prog)s --set-pages 524288 # Set to 524288 pages + %(prog)s --delete # Delete the variable + ''', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--read', action='store_true', + help='Read the current TTMPageLimit value') + group.add_argument('--set', metavar='SIZE', + help='Set TTMPageLimit (e.g., 2G, 512M)') + group.add_argument('--set-pages', metavar='PAGES', type=int, + help='Set TTMPageLimit as raw page count') + group.add_argument('--delete', action='store_true', + help='Delete the TTMPageLimit variable') + + args = parser.parse_args() + + check_root() + check_efi_available() + + if args.read: + read_efi_variable() + elif args.set: + pages = parse_size(args.set) + write_efi_variable(pages) + elif args.set_pages: + write_efi_variable(args.set_pages) + elif args.delete: + delete_efi_variable() + + +if __name__ == '__main__': + main() -- 2.53.0
