// SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2018,2019 IBM Corp. #define _GNU_SOURCE #include "ast.h" #include "console.h" #include "debug.h" #include "log.h" #include "prompt.h" #include "ts16.h" #include "tty.h" #include #include #include #include #include #include #include #include static inline int streq(const char *a, const char *b) { return !strcmp(a, b); } static int ts16_console_init(struct debug *ctx, va_list args) { const char *ip, *username, *password; struct ts16 *ts16; int port; int fd; ts16 = malloc(sizeof(*ts16)); if (!ts16) return -ENOMEM; ip = va_arg(args, const char *); port = va_arg(args, int); username = va_arg(args, const char *); password = va_arg(args, const char *); fd = ts16_init(ts16, ip, port, username, password); if (fd < 0) { goto cleanup_free; } ctx->console = &ts16->console; return fd; cleanup_free: free(ts16); return fd; } static int tty_console_init(struct debug *ctx, const char *path) { struct tty *tty; int fd; tty = malloc(sizeof(*tty)); if (!tty) return -ENOMEM; fd = tty_init(tty, path); if (fd < 0) { goto cleanup_free; } ctx->console = &tty->console; return fd; cleanup_free: free(tty); return fd; } int debug_init(struct debug *ctx, ...) { va_list args; int rc; va_start(args, ctx); rc = debug_init_v(ctx, args); va_end(args); return rc; } int debug_init_v(struct debug *ctx, va_list args) { const char *interface; int rc, fd; /* * Sanity-check presence of the password, though we also test again below * where we use it to avoid TOCTOU. */ if (!getenv("AST_DEBUG_PASSWORD")) { loge("AST_DEBUG_PASSWORD environment variable is not defined\n"); return -ENOTSUP; } interface = va_arg(args, const char *); if (!interface) return -EINVAL; if (streq("digi,portserver-ts-16", interface)) { fd = ts16_console_init(ctx, args); } else { fd = tty_console_init(ctx, interface); } if (fd < 0) { loge("Failed to initialise the console (%s): %d\n", interface, fd); return fd; } rc = prompt_init(&ctx->prompt, fd, "\r", false); if (rc < 0) { goto cleanup_ts16; } return 0; cleanup_ts16: console_destroy(ctx->console); return rc; } int debug_destroy(struct debug *ctx) { int rc = 0; rc |= prompt_destroy(&ctx->prompt); rc |= console_destroy(ctx->console); return rc ? -EBADF : 0; } int debug_enter(struct debug *ctx) { const char *password; int rc; logi("Entering debug mode\n"); password = getenv("AST_DEBUG_PASSWORD"); if (!password) { loge("AST_DEBUG_PASSWORD environment variable is not defined\n"); return -ENOTSUP; } rc = console_set_baud(ctx->console, 1200); if (rc < 0) return rc; rc = prompt_write(&ctx->prompt, password, strlen(password)); if (rc < 0) goto cleanup_port_password; rc = prompt_expect(&ctx->prompt, "$ "); if (rc < 0) goto cleanup_port_password; rc = console_set_baud(ctx->console, 115200); if (!rc) sleep(1); return rc; cleanup_port_password: console_set_baud(ctx->console, 115200); prompt_run(&ctx->prompt, ""); return rc; } int debug_exit(struct debug *ctx) { int rc; logi("Exiting debug mode\n"); rc = prompt_run(&ctx->prompt, "q"); if (rc < 0) return rc; sleep(1); prompt_run(&ctx->prompt, ""); return console_set_baud(ctx->console, 115200); } int debug_probe(struct debug *ctx) { int rc; logd("Probing %s\n", ahb_interface_names[ahb_debug]); rc = debug_enter(ctx); if (rc < 0) return rc; return debug_exit(ctx); } static ssize_t debug_parse_d(char *line, char *buf) { char *eoa, *words, *token, *stream; char *saveptr; char *cursor; ssize_t rc; /* Strip leading address */ eoa = strchr(line, ':'); if (!eoa) return -EBADE; /* Extract 4-byte values */ words = eoa + 1; cursor = buf; stream = strdup(words); while ((token = strtok_r(words, " ", &saveptr))) { rc = sscanf(token, "%02hhx%02hhx%02hhx%02hhx", &cursor[3], &cursor[2], &cursor[1], &cursor[0]); if (rc < 0) { rc = -errno; goto done; } if (rc < 4) { loge("Wanted 4 but extracted %zd bytes from token '%s' in words '%s' from line '%s'\n", rc, token, words, line); rc = -EBADE; goto done; } cursor += rc; words = NULL; } rc = cursor - buf; done: free(stream); return rc; } static int debug_read_fixed(struct debug *ctx, char mode, uint32_t phys, uint32_t *val) { char buf[100], *response, *prompt; char *command; unsigned long parsed; int rc; if (!(mode == 'i' || mode == 'r')) return -EINVAL; rc = asprintf(&command, "%c %x", mode, phys); if (rc < 0) return -errno; prompt = &buf[0]; rc = prompt_run_expect(&ctx->prompt, command, "$ ", &prompt, sizeof(buf)); free(command); if (rc < 0) return rc; /* Terminate the string by overwriting the prompt */ *prompt = '\0'; /* Discard echoed response */ response = strchr(buf, ctx->prompt.eol[0]); if (!response) return -EIO; /* Extract the data */ errno = 0; parsed = strtoul(response, NULL, 16); if (errno == ERANGE && (parsed == LONG_MAX || parsed == LONG_MIN)) return -errno; *val = parsed; return 0; } #define DEBUG_D_MAX_LEN (128 * 1024) ssize_t debug_read(struct debug *ctx, uint32_t phys, void *buf, size_t len) { char line[2 * sizeof("20002ba0:31e01002 20433002 30813003 e1a06002\r\n")]; size_t remaining = len; size_t ingress; char *command; char *cursor; ssize_t rc; if (len < 4) { uint32_t val = 0; cursor = buf; while (remaining) { rc = debug_read_fixed(ctx, 'i', phys, &val); if (rc < 0) return rc; *cursor++ = val & 0xff; remaining--; phys++; } return len; } cursor = buf; do { size_t consumed; int found; retry: ingress = remaining > DEBUG_D_MAX_LEN ? DEBUG_D_MAX_LEN : remaining; rc = asprintf(&command, "d %x %zx", phys, ingress); if (rc < 0) return -errno; rc = prompt_run(&ctx->prompt, command); free(command); if (rc < 0) return rc; /* Eat the echoed command */ do { found = prompt_gets(&ctx->prompt, line, sizeof(line)); if (found < 0) return found; } while (!strcmp("$ \n", line)); /* Deal any prompt from a prior run */ consumed = 0; do { found = prompt_gets(&ctx->prompt, line, sizeof(line)); if (found < 0) return found; rc = debug_parse_d(line, cursor); if (rc < 0) { rc = prompt_run(&ctx->prompt, ""); if (rc < 0) return rc; rc = prompt_expect(&ctx->prompt, "$ "); if (rc < 0) return rc; loge("Failed to parse line '%s'\n", line); loge("Retrying from address 0x%"PRIx32"\n", phys); goto retry; } cursor += rc; consumed += rc; } while (consumed < ingress); /* * Normally we would prompt_expect() here, but prompt_gets() has likely * swallowed the prompt, so we'll YOLO and just assume it's done. */ phys += ingress; remaining -= ingress; } while(remaining); return len; } #define DEBUG_CMD_U_MAX 128 ssize_t debug_write(struct debug *ctx, uint32_t phys, const void *buf, size_t len) { const void *cursor; size_t remaining; size_t egress; char *command; char mode; int rc; if (len <= 4) { size_t remaining; remaining = len; cursor = buf; while (remaining) { rc = asprintf(&command, "o %x %hhx", phys, *(const uint8_t *)cursor); if (rc < 0) return -errno; rc = prompt_run(&ctx->prompt, command); if (rc < 0) return rc; rc = prompt_expect(&ctx->prompt, "$ "); if (rc < 0) return rc; if (rc == 0) return -EINVAL; cursor++; phys++; remaining--; } return len; } mode = 'u'; remaining = len; cursor = buf; do { egress = remaining > DEBUG_CMD_U_MAX ? DEBUG_CMD_U_MAX : remaining; rc = asprintf(&command, "%c %x %zx", mode, phys, egress); if (rc < 0) return -errno; rc = prompt_run(&ctx->prompt, command); free(command); if (rc < 0) return rc; rc = prompt_write(&ctx->prompt, (const char *)cursor, egress); if (rc < 0) return rc; rc = prompt_expect(&ctx->prompt, "$ "); if (rc < 0) return rc; phys += egress; cursor += egress; remaining -= egress; } while (remaining); return len; } int debug_readl(struct debug *ctx, uint32_t phys, uint32_t *val) { return debug_read_fixed(ctx, 'r', phys, val); } int debug_writel(struct debug *ctx, uint32_t phys, uint32_t val) { char *command; int rc; rc = asprintf(&command, "w %x %x", phys, val); if (rc < 0) return -errno; rc = prompt_run(&ctx->prompt, command); if (rc < 0) return rc; if (!((phys & ~0x20) == (AST_G5_WDT | WDT_RELOAD) && val == 0)) { rc = prompt_expect(&ctx->prompt, "$ "); if (rc < 0) return rc; if (rc == 0) return -EINVAL; } return 0; }