summaryrefslogtreecommitdiff
path: root/util/ec_openocd.py
blob: e0a15bd08b38f4b1edf9f8135b1262676815aebd (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3

# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import dataclasses
import pathlib
import socket
import subprocess
import sys
import time

"""
Flashes and debugs the EC through openocd
"""


@dataclasses.dataclass
class BoardInfo:
    gdb_variant: str
    num_breakpoints: int
    num_watchpoints: int


# Debuggers for each board, OpenOCD currently only supports GDB
boards = {"skyrim": BoardInfo("arm-none-eabi-gdb", 6, 4)}


def create_openocd_args(interface, board):
    if not board in boards:
        raise RuntimeError(f"Unsupported board {board}")

    board_info = boards[board]
    args = [
        "openocd",
        "-f",
        f"interface/{interface}.cfg",
        "-c",
        "add_script_search_dir openocd",
        "-f",
        f"board/{board}.cfg",
    ]

    return args


def create_gdb_args(board, port, executable):
    if not board in boards:
        raise RuntimeError(f"Unsupported board {board}")

    board_info = boards[board]
    args = [
        board_info.gdb_variant,
        executable,
        # GDB can't autodetect these according to OpenOCD
        "-ex",
        f"set remote hardware-breakpoint-limit {board_info.num_breakpoints}",
        "-ex",
        f"set remote hardware-watchpoint-limit {board_info.num_watchpoints}",
        # Connect to OpenOCD
        "-ex",
        f"target extended-remote localhost:{port}",
    ]

    return args


def flash(interface, board, image, verify):
    print(f"Flashing image {image}")
    # Run OpenOCD and pipe its output to stdout
    # OpenOCD will shutdown after the flashing is completed
    args = create_openocd_args(interface, board)
    args += ["-c", f'init; flash_target "{image}" {int(verify)}; shutdown']

    subprocess.run(args, stdout=sys.stdout, stderr=subprocess.STDOUT)


def debug(interface, board, port, executable):
    # Start OpenOCD in the background
    openocd_args = create_openocd_args(interface, board)
    openocd_args += ["-c", f"gdb_port {port}"]

    openocd = subprocess.Popen(
        openocd_args,
        encoding="utf-8",
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )

    # Wait for OpenOCD to start, it'll open a port for GDB connections
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connected = False
    for i in range(0, 10):
        print("Waiting for OpenOCD to start...")
        connected = sock.connect_ex(("localhost", port))
        if connected:
            break

        time.sleep(1000)

    if not connected:
        print(f"Failed to connect to OpenOCD on port {port}")
        return

    sock.close()

    gdb_args = create_gdb_args(board, port, executable)
    # Start GDB
    gdb = subprocess.Popen(
        gdb_args, stdout=sys.stdout, stderr=subprocess.STDOUT, stdin=sys.stdin
    )

    openocd_out = ""
    while gdb.poll() == None and openocd.poll() == None:
        (output, _) = openocd.communicate()
        openocd_out += output

    # Wait for OpenOCD to shutdown
    print("Waiting for OpenOCD to finish...")
    if openocd.poll() == None:
        try:
            # Read the last bit of stdout
            (output, _) = openocd.communicate(timeout=3)
            openocd_out += output
        except subprocess.TimeoutExpired:
            # OpenOCD didn't shutdown, kill it
            openocd.kill()

    if openocd.returncode != 0:
        print("OpenOCD failed to shutdown cleanly: ")
    print(openocd_out)


def get_flash_file(board):
    return (
        pathlib.Path(__file__).parent
        / ".."
        / "build"
        / "zephyr"
        / board
        / "output"
        / "ec.bin"
    ).resolve()


def get_executable_file(board):
    return (
        pathlib.Path(__file__).parent
        / ".."
        / "build"
        / "zephyr"
        / board
        / "output"
        / "zephyr.ro.elf"
    ).resolve()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--board",
        "-b",
        choices=boards.keys(),
        required=True,
    )

    parser.add_argument(
        "--interface",
        "-i",
        default="jlink",
        help="The JTAG interface to use",
    )

    parser.add_argument(
        "--file",
        "-f",
        type=pathlib.Path,
        default=None,
        help="The file to use, see each sub-command for what the file is used for",
    )

    sub_parsers = parser.add_subparsers(help="sub-command -h for specific help")
    flash_parser = sub_parsers.add_parser(
        "flash",
        help="Flashes an image to the target EC, \
        FILE selects the image to flash, defaults to the zephyr image",
    )
    flash_parser.set_defaults(command="flash")
    flash_parser.add_argument(
        "--verify",
        "-v",
        default=True,
        help="Verify flash after writing image, defaults to true",
    )

    debug_parser = sub_parsers.add_parser(
        "debug",
        help="Debugs the target EC through GDB, \
        FILE selects the executable to load debug info from, defaults to using the zephyr RO executable",
    )
    debug_parser.set_defaults(command="debug")
    debug_parser.add_argument(
        "--port",
        "-p",
        help="The port for GDB to connect to",
        type=int,
        default=3333,
    )

    args = parser.parse_args()
    # Get the image path if we were given one
    target_file = None
    if args.file != None:
        target_file = args.file.resolve()

    if args.command == "flash":
        image_file = (
            get_flash_file(args.board) if target_file == None else target_file
        )
        flash(args.interface, args.board, image_file, args.verify)
    elif args.command == "debug":
        executable_file = (
            get_executable_file(args.board)
            if target_file == None
            else target_file
        )
        debug(args.interface, args.board, args.port, executable_file)
    else:
        parser.print_usage()


if __name__ == "__main__":
    main()