summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/tick_gen.py
blob: d2c176bc16519c9df96f10e62a5006028343d36c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026 Open-IOV contributors
# tick_gen.py - keep the guest-RM timer tick page live in the host-side
# /dev/shm/vgpu-guest-ram image.  The guest's timer vmap page is read as a
# u32 millisecond counter; writing a strictly increasing u32 there at 1 ms
# (or --ms) keeps the guest RM's timer path moving.
#
# Usage: sudo python3 tools/guest-diag/tick_gen.py <gpa_hex> [--ms 1] [--file PATH]
#   <gpa_hex>  guest physical address of the timer tick u32 (the guest's
#              timer vmap base + 0x6c004)
#   --ms N     write interval in milliseconds (default 1)
#   --file P   guest RAM file (default /dev/shm/vgpu-guest-ram)
#
# The file offset is gpa - 0x80000000.  The mapping is created once; the loop
# only writes the u32.  SIGINT stops cleanly.
import argparse
import mmap
import os
import signal
import struct
import sys
import time

RAM_BASE = 0x80000000
DEFAULT_FILE = "/dev/shm/vgpu-guest-ram"
stop = False


def on_sigint(signum, frame):
    global stop
    stop = True


def main():
    ap = argparse.ArgumentParser(add_help=True)
    ap.add_argument("gpa", help="guest physical address (hex, 0x prefix optional)")
    ap.add_argument("--ms", type=float, default=1.0,
                    help="write interval in milliseconds (default 1)")
    ap.add_argument("--file", default=DEFAULT_FILE, help="guest RAM file")
    args = ap.parse_args()

    try:
        gpa = int(args.gpa, 0)
    except ValueError:
        gpa = int(args.gpa, 16)
    if gpa < RAM_BASE:
        print("tick_gen: gpa 0x%x is below 0x%x" % (gpa, RAM_BASE),
              file=sys.stderr)
        return 2
    off = gpa - RAM_BASE

    fd = os.open(args.file, os.O_RDWR)
    try:
        size = os.fstat(fd).st_size
        if off + 4 > size:
            print("tick_gen: offset 0x%x beyond %s (0x%x bytes)"
                  % (off, args.file, size), file=sys.stderr)
            return 2
        mm = mmap.mmap(fd, size, access=mmap.ACCESS_WRITE)
        try:
            signal.signal(signal.SIGINT, on_sigint)
            interval = max(0.0, args.ms / 1000.0)
            counter = time.monotonic_ns() // 1_000_000
            iterations = 0
            last_print = time.monotonic()
            print("tick_gen: file=%s gpa=0x%x off=0x%x interval=%.3fms"
                  % (args.file, gpa, off, args.ms), flush=True)
            while not stop:
                now_ms = time.monotonic_ns() // 1_000_000
                if now_ms <= counter:
                    now_ms = counter + 1
                counter = now_ms
                val = counter & 0xFFFFFFFF
                struct.pack_into("<I", mm, off, val)
                iterations += 1
                t = time.monotonic()
                if t - last_print >= 2.0:
                    print("tick_gen: iterations=%d value=%d (0x%08x)"
                          % (iterations, val, val), flush=True)
                    last_print = t
                if interval:
                    time.sleep(interval)
            print("tick_gen: stopped iterations=%d value=0x%08x"
                  % (iterations, counter & 0xFFFFFFFF), flush=True)
        finally:
            mm.close()
    finally:
        os.close(fd)
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        sys.exit(0)